Draw a rectangle on the floor and it fills with chairs. Sliders set the spacing,
the aisles and the seat count.
Loading Space Builder…
Space Builder · Add Tool
Drawing No.1 / 5
Scale1:1
Weight37.8KiB
Date
Projection
Loading the area…
Place Area
Space Builder · Add Tool
Drawing No.2 / 5
Scale1:1
Weight37.8KiB
Date
Projection
Loading Options…
Edit Parameters
Space Builder · Add Tool
Drawing No.3 / 5
Scale1:1
Weight37.8KiB
Date
Projection
Loading layouts…
Layout Styles
Space Builder · Add Tool
Drawing No.4 / 5
Scale1:1
Weight37.8KiB
Date
Projection
Loading badge…
Tag offset
φ is the camera's angle relative to the area; r is the distance from center to the area's
silhouette edge at that angle.
Capacity Badge
Space Builder · Add Tool
Drawing No.5 / 5
Scale1:1
Weight37.8KiB
Date
Projection
thereismoreunderneath,turnthepage
thiscornerdragsthepageback
BDetail BScale 1:1 マーカーをつくる Marker editor
Title
Interactive Map Marker Editor
Medium
React, Leaflet, Redux
Year
Notes
A pin editor for venue maps. Shape, border, fill and decoration are separate
parts, and a saved pin rebuilds itself from them.
Interactive Map Marker Editor
Drawing No.1 / 6
Scale1:1
Weight53.8KiB
Date
Projection
Lobby
Cafe
Plaza
Marker Selector
Interactive Map Marker Editor
Drawing No.2 / 6
Scale1:1
Weight53.8KiB
Date
Projection
ReactDragging outside the render loop
The whole interactive map is a React app, so the editor started as a regular component.
That held until I dragged a control point: every pointermove re-rendered a
large subtree (solve constraints, preview SVG, update indicators, update sidebar) and the
frame rate fell apart.
On drag, the handlers now mutate MarkerPart.controlPoints directly, then:
Circles and crosses move by CSS translate through DOM refs.
Each editor step is a class with its own imperativeReactComponent, a
createRoot plus an .update() method, so only the part that
changed is redrawn.
Widgets that still need React (colours, text, icons) read an external
reactiveState through useSyncExternalStore, so React re-renders
only once that store is notified.
Circles: the handle you drag
Crosses: where the constraint put it
Sidebar: the parts a marker is made of
Marker Editor
Interactive Map Marker Editor
Drawing No.3 / 6
Scale1:1
Weight53.8KiB
Date
Projection
Preview chromeCrosshair background
Failed
Overlapping linear-gradients
H + V stripe layers stack into a grid, but each axis rounds its stops alone, so the
columns drift.
Shipped
SVG tile · repeat × 17
One tiny SVG draws a centred + from two short strokes in a
viewBox="-1 -1 2 2". background-repeat tiles that cell so
every cross stays coherent.
H stripes
Rows, no columns
+V stripes
Columns, no rows
→Overlap = grid
Crosses where lines meet
SVG cell
Short +, then tiled
Preview Background
Interactive Map Marker Editor
Drawing No.4 / 6
Scale1:1
Weight53.8KiB
Date
Projection
MarkerPartComposable stack
Shape Border Fill Decoration Composed
+
+
+
→
Serialisation
Save writes typed data-* attrs (Point: /
Number: / String:). Load rebuilds the parts.
One shared group id, so Ctrl+Z or Cmd+Z undoes the whole batch.
Undoable Store
Interactive Map Marker Editor
Drawing No.6 / 6
Scale1:1
Weight53.8KiB
Date
Projection
flipthecornerforthenextsheet
thiscornerdragsthepageback
CDetail CScale 1:1 ドラッグ&ドロップ Drag and drop
Title
Space Builder · Drag and Drop
Medium
Vue, Three.js
Year
Notes
Drag a card off the catalogue and the object lands where it is dropped. Double-click
one instead and the next click on the floor places it.
Loading catalog…
Space Builder · Drag & Drop
Drawing No.1 / 2
Scale1:1
Weight37.8KiB
Date
Projection
Picture over the catalog, model over the floor
Four moments of one drag: the card's picture follows the pointer, the model takes
over the moment the pointer crosses onto the floor, and letting go places it.
drag the object to the viewportover the catalog it is still a pictureover the floor it becomes the modellet go and it is placed
Picture to Model
Space Builder · Drag & Drop
Drawing No.2 / 2
Scale1:1
Weight37.8KiB
Date
Projection
onemoresheet,peelthiscorner
thiscornerdragsthepageback
DDetail DScale 1:1 フォントをえらぶ Font picker
Title
Interactive Map Font Picker
Medium
React
Year
Notes
The list of faces comes from document.fonts, each face's Unicode subsets folded
back into one entry. Nothing in it is hardcoded, so a font that loads later shows
up on its own.
Loading the font picker…
Interactive Map Font Picker
Drawing No.1 / 4
Scale1:1
Weight33.7KiB
Date
Projection
Type a URL
Embed URLrust-lang.org
A page the visitor names in the Embed URL subform
Fetch the page
iframex-frame-options: deny/api/font-proxyone origin, so the HTML can be read
rust-lang.org refuses to be framed, so a worker on this origin fetches the HTML
The browser follows every link and @import. It makes each
src absolute and routes it through the proxy, since webfont loads are
CORS-checked too
newFontFace().load() document.fonts.add()
Show the new face
Poppins 400Alfa Slab One 400
The dropdown re-reads document.fonts on loadingdone. The dot
marks what the last source brought
A Google font skips the first two steps. fonts.googleapis.com/css2?family=Lobster
returns the same kind of stylesheet, which joins the flow here
Font Extraction
Interactive Map Font Picker
Drawing No.2 / 4
Scale1:1
Weight33.7KiB
Date
Projection
The field's own border reports the fetch. A quarter turn orbits while the request is
out, then the ring closes from wherever the orbit had got to, green for a font that
exists and red for one that does not
cubic-bezier(0.75,0.25,0.2,0.2) gives the orbit a push and lets it
settle. Kept for every turn it stutters at each restart, so at 750ms the script swaps
the timing function for linear at the point the curve has already
reached. The seam goes, the first turn keeps its kick
One element, two backgrounds. The gradient is painted on the border box and the
field's own colour covers the padding box over it, so the only part left showing is
the border. Three custom properties move it: where the arc starts, how much of the
turn is coloured, and the colour. @property types them as angles and a
colour, which is what makes them animatable at all
Loading Indicator
Interactive Map Font Picker
Drawing No.3 / 4
Scale1:1
Weight33.7KiB
Date
Projection
Font Size sets the column's own text size. Drag it and the row grows, so the slider walks
out from under the pointer. FixedElement pins the row where the drag started. Both columns
get the same drag, with the pointer held in one place
Loading the two columns…
Held Controls
Interactive Map Font Picker
Drawing No.4 / 4
Scale1:1
Weight33.7KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
EDetail EScale 3:1 読みこみ中のロゴ Loading logo
Title
Visrez Animated Loading Logo
Medium
SVG, CSS animation
Year
Notes
The Illustrator export became one path and a cube, animated by hand. The sheets
after it show each step.
Visrez Animated Loading Logo
Drawing No.1 / 6
Scale3:1
Weight1.7KiB
Date
Projection
Original Logo
Visrez Animated Loading Logo
Drawing No.2 / 6
Scale3:1
Weight7.5KiB
Date
Projection
Cube :)
Visrez Animated Loading Logo
Drawing No.3 / 6
Scale3:1
Weight1.7KiB
Date
Projection
Authored Path
Visrez Animated Loading Logo
Drawing No.4 / 6
Scale3:1
Weight1.7KiB
Date
Projection
<svgxmlns="http://www.w3.org/2000/svg"viewBox="-1 -1 2 2"-- Define the coordinate system to go-- from (-1, -1) with a width of (2, 2)><gstyle="scale: 0.707;">-- Scale the overall shape down. Because-- we move up (and down) by 2 * 0.707,-- the overall height becomes 4 * 0.707 = 2.828,-- so we scale down by 0.707 to get a height of 2<pathd="M0.6,-0.42-- Move to 60% of (1, -0.707)L0,0-- Draw a line to (0, 0)l-1,-0.707-- Draw a relative line by moving (-1, -0.707)l1,-0.707-- Draw a relative line by moving ( 1, -0.707)l1,0.707-- Draw a relative line by moving ( 1, 0.707)v1-- Draw a vertical line by moving (0, 1)L0,1-- Draw a line by moving to (0, 1)l-1,-0.707-- Draw a relative line by moving (-1, -0.707)v-0.2"-- Draw a vertical line by moving (0, -0.2)pathLength="1"-- Tell the renderer to treat the length-- of the path as being 1/></g></svg>
Path Data
Visrez Animated Loading Logo
Drawing No.5 / 6
Scale3:1
Weight1.7KiB
Date
Projection
Putting It All Together
Visrez Animated Loading Logo
Drawing No.6 / 6
Scale3:1
Weight1.7KiB
Date
Projection
fivemoresheets,peelthiscorner
thiscornerdragsthepageback
FDetail FScale 1:1 テーマをえらぶ Theme picker
Title
Theme Picker
Medium
Astro, CSS, View Transitions
Year
Notes
The control in the corner that picks one of this page's four themes. A pick grows
the icon it was picked with from the corner until it covers the page, and the new
page is cut out of the old along it. The first sheet plays it on a sample page with its
own theme. The sheets behind it draw the wipe, its code, the tokens and who decides.
LightDarkArcticForest
stagedata-demo-theme="light""dark""arctic""dark-forest"unset, so the OS scheme decides
Theme Picker
Drawing No.1 / 5
Scale1:1
Weight6.8KiB
Date
Projection
The visible icon drives the animation by creating a starting point from its placement and an end point with that same icon sized to cover the whole viewport
123
× 2.4
× 1.2
× 2
× 1.4
1First frame
The checked icon's <path>, mapped from its viewBox onto its box in the corner.
2Last frame
The same path, grown to a square the viewport's diagonal × the theme's factor wide.
3Between
clip-path from the one to the other, 500 ms ease-in, over a picture of the old page. Off under reduced motion.
The Wipe
Theme Picker
Drawing No.2 / 5
Scale1:1
Weight6.8KiB
Date
Projection
The wipe's script in five steps, and the geometry of its last frame
1Start the transition with startViewTransition, the switch as its callback
ThemePicker.astro · _colors.scssFour seeds per theme, three ramps mixed from them, and the names every sheet reads
seeds
canvas
ink
accent
blueprint
color-mix(in oklch)
neutral
100
200
300
400
500
600
650
700
800
850
875
900
accent
100
200
300
400
500
600
650
700
800
850
875
900
blueprint
100
200
300
400
500
600
650
700
800
850
875
900
var()names
--surface-page
--ink
--line
--accent
--blueprint
Seeds
Four oklch colours on :root per theme. A dark theme's ink is light, so its ramps run the other way by themselves.
Ramps
A Sass loop mixes canvas into ink in eleven steps for neutral. accent and blueprint tint to 700, take the seed at 800 and shade to 900.
Names
What a sheet reads, never a step. A blueprint sheet like this one remaps them from its own paper.
Token Layers
Theme Picker
Drawing No.4 / 5
Scale1:1
Weight6.8KiB
Date
Projection
ThemePicker.astro · Layout.astroThe OS decides the theme until a pick, and from then on the stamp decides, before the first paint
OS schemepainting@media(prefers-color-scheme: dark){:root:not([data-theme]) }the OS only knows light and dark
stamppainting:root[data-theme="dark"]written by the pick, kept as cv-theme
radiopainting:root:has(#theme-dark:checked)works without script
localStorage
<head> script
data-theme on <html>
first paint
On a reload, eight lines in the <head> stamp the kept pick before the first paint
Nothing picked
Only the OS rule matches, so the page follows the system and changes with it.
A pick
Writes the stamp, checks the radio, keeps cv-theme. color-scheme: only on <body> takes scrollbars and controls along.
Radios by id
An attribute inside :has() put every input on the page in :root's invalidation set; an id names one.
Default and Override
Theme Picker
Drawing No.5 / 5
Scale1:1
Weight6.8KiB
Date
Projection
fourmoresheets,peelthiscorner
thiscornerdragsthepageback
GDetail GScale 1:1 いろいろなしゅるい Object variants
Title
Space Builder · Object Variants
Medium
Vue, Sass
Year
Notes
One card covers a whole group of library objects. The carousel steps through one
cell's finishes, the dropdowns move between cells, and a pair the catalogue never
made falls back to the nearest one it did.
Space Builder · Object Variants
Drawing No.1 / 3
Scale1:1
Weight20.3KiB
Date
Projection
CatalogObjectField.vue · CatalogObjectThumbnailOverlay.vue · HoverSelect.vueOne card stands for every library object in its group
15
25
35
45
55
Chair42cm x 50cm x 95cm
Banquet Table8 seats
2.4m x 1.2m
Group
Every library object under one catalog entry: 5 chairs, 4 banquet sets
Cells
pax and size cut the group, 3 seat counts by 2 sizes here; a cell's objects are its styles
Preview
An option previews on the card before you pick it
Variant Groups
Space Builder · Object Variants
Drawing No.2 / 3
Scale1:1
Weight20.3KiB
Date
Projection
CatalogObjectFooter.vue · isAvailablePaxWhat happens when the combination does not exist
Banquet Table6 seats
1.8m x 76cm
Every seat count stays on the list. The 8 and 4 seat sets have no
object at the size held, so they go grey. Grey is not disabled.
Try the pairSame seat count, and the size already showing
Else take the seat countThe first object with that many seats, whatever its size
Both readings moveThe size under the list changes to match what was found
Never disabled
A greyed option is still clickable. Grey says the other axis will move, not that the pick is refused
Sorted
Every value sorts in one descending run. Missing ones go grey and keep their place
Nothing selected
With no object picked yet, every value counts as available
Missing Variants
Space Builder · Object Variants
Drawing No.3 / 3
Scale1:1
Weight20.3KiB
Date
Projection
twomoresheets,peelthiscorner
thiscornerdragsthepageback
HDetail HScale 1:1 タグをつけるツール Tagging tool
Title
Library Tagging Tool
Medium
CSS, JavaScript
Year
Notes
One input tags a base object and every variant under it. Variants that already
disagree keep their own value, and an object leaves the page once nothing about it
is missing. Pick another property and the values already stored for it come back.
Library Tagging Tool
Drawing No.1 / 4
Scale1:1
Weight12.9KiB
Date
Projection
One "Shared value" input for the base object and every variant
Banquet Set · shared value
Ivory✓
keyup · focus · click · blur
12 seats · baseIvory--caret:5
11 seatsIvory--caret:5
10 seatsIvory--caret:5
0 seatsIvory--caret:5
Mirror
Every keystroke copies the value into each variant card's own input, scrolled to its end so the tail stays in view
Save
Finishing the field submits with update_styles: one row per object, titleized, and a cleared value destroys the rows
Warn
Variants that already disagree keep the input open but empty; the placeholder lists the overrides and the tick becomes a warning sign
2Each variant form gets --caret and its input's --input-width; the value is copied in and scrolled to its end
3The form is monospace, so 1ch is one character and a ::before holding a literal | lands where the real caret would
4It only paints while the shared input :focus holds, blinks with opacity keyframes, and stands still under prefers-reduced-motion
Simulated Caret
Library Tagging Tool
Drawing No.3 / 4
Scale1:1
Weight12.9KiB
Date
Projection
Fill the last gap, and the object leaves the list
A save is find_or_initialize_by then save!; an emptied field destroys the row.
Completed Objects
Library Tagging Tool
Drawing No.4 / 4
Scale1:1
Weight12.9KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
IDetail IScale 1:1 かみをめくる Paper stack
Title
Paper Stack
Medium
CSS, TypeScript
Year
Notes
Every demo on this page is a stack of drawing sheets, and the dog-ear turns them.
These three sheets take the stack apart: the pile and its front page, the fold's
three layers and the release that turns a page, and the row the stack becomes
without JavaScript.
Paper Stack
Drawing No.1 / 3
Scale1:1
Weight26KiB
Date
Projection
How a fold is drawn
the page, cut on the crease
the shade, on the clip wrapper
the flap, the back mirrored over
The Fold
Paper Stack
Drawing No.2 / 3
Scale1:1
Weight26KiB
Date
Projection
The stack without JavaScript
One rule carries all of it: @media(scripting: none).
Without JavaScript
Paper Stack
Drawing No.3 / 3
Scale1:1
Weight26KiB
Date
Projection
twomoresheets,peelthiscorner
thiscornerdragsthepageback
JDetail JScale 1:1 できごとをくばる GNU social event bus
Title
GNU social · Event Dispatch
Medium
PHP, Symfony
Year
Notes
GNU social is a federated social network server. Its plugins never call each other:
a plugin listens for an event by naming a method after it, and the first one to
return stop has the job. Switch ImageEncoder off and no other plugin claims the JPEG,
so the template draws a plain link. ImageEncoder shows a GIF too, then asks for its
thumbnail with a second event, FileResizerAvailable. That one has two takers at the
same priority. VideoEncoder asked for image/gif by name, so it goes first and keeps the
GIF moving. Switch it off and ImageEncoder makes a still.
Each listener is [Module,'onViewAttachment'], in the order the modules were found
ImageEncoder's view asks for the GIF's thumbnail, and getOrCreate raises
this. Each listener is [Module,'onFileResizerAvailable']
dispatch
AudioEncoderprio 0
next
ImageEncoderprio 0
stop
VideoEncoderprio 0
skipped
returns stop
Not loaded
{%forblockin handle_event('ViewAttachment', …)%}{{ block | raw }}{%endfor%}{%ifnot handled %}<ahref="{{ attachment.getShowUrl(note)}}">{{ title }}</a>{%endif%}
no block came back, so the template writes its own linka block came back, so the template's link is not written
then getOrCreate tries the resizers in this orderarray_merge($event_map['image/gif'],$event_map['image'])
'image/gif'VideoEncoder::resizeVideoPathtrue
'image'ImageEncoder::resizeImagePathnot called
Every module handler registers at the same priority, so GNU social orders these by how
specific the mimetype is.
One handler returns Event::stop, and propagation ends there
AudioEncoder hears the JPEG first and answers next. ImageEncoder claims it and
appends its view to $res. VideoEncoder never hears the event.
Not this handler's job, or its part is done and others may add theirs. Every encoder answers next to a mimetype that is not its own
case stopchain halts
Claimed the job. Nothing after it hears the event, and $res holds what it appended
case unhandledchain goes on
Declared in the enum, returned by no handler in the tree: a handler with nothing to add says next
EventResult
GNU social · Event Dispatch
Drawing No.2 / 4
Scale1:1
Weight17.9KiB
Date
Projection
No module registers a listener
Each directory's main class in components/*/ and plugins/*/ is a
module. Its public on<EventName> methods listen for that event.
public static function version(): stringnot a listener
public static function shouldHandle(string $mimetype): boolnot a listener
public function onFileMetaAvailable(array &$event_map, string $mimetype): EventResultbecomesFileMetaAvailable[ImageEncoder,'onFileMetaAvailable']prio 0
public function onFileSanitizerAvailable(array &$event_map, string $mimetype): EventResultbecomesFileSanitizerAvailable[ImageEncoder,'onFileSanitizerAvailable']prio 0
public function onFileResizerAvailable(array &$event_map, string $mimetype): EventResultbecomesFileResizerAvailable[ImageEncoder,'onFileResizerAvailable']prio 0
public function onViewAttachment(array $vars, array &$res): EventResultbecomesViewAttachment[ImageEncoder,'onViewAttachment']prio 0
public function resizeImagePath(string $source, …): boolnot a listener
public function onPluginVersion(array &$versions): EventResultbecomesPluginVersion[ImageEncoder,'onPluginVersion']prio 0
Found by reflection in a compiler pass, once, when the container is built. The table is
written into the compiled container, so a request pays nothing for the wiring.
Module Discovery
GNU social · Event Dispatch
Drawing No.3 / 4
Scale1:1
Weight17.9KiB
Date
Projection
The wrong return type fails the build, not a request
The same compiler pass that finds a handler reads its declared return type, and anything
but EventResult throws BugFoundException before the container
is written.
$ bin/console cache:clear
// Clearing the cache for the dev environment with debug trueIn ModuleManager.php line 93:
[App\Util\Exception\BugFoundException]$ tail -n 1 var/log/dev.log
app.CRITICAL: Return type of Plugin\ImageEncoder\ImageEncoder::onViewAttachment is not the required EventResult in src/Core/ModuleManager.php:93
A handler that declares no return type gets past the pass. The dispatch catches it the
first time it returns nothing (src/Core/Event.php:93): Events must return
an \EventResult, which {file} for {event} does not.
A command-line tool that runs a small federated network on one machine, so you can
test how fediverse servers talk to each other before a release. Each server gets its
own hostname, database and branch. One YAML file in, a Docker Compose stack out.
Fediverse Playground runs a small federated network on one machine, to test how
fediverse servers talk to each other. Switch servers on and off to redraw the
docker-compose.yml it writes.
# This file was generated by Fediverse Playground (GPL3+)version:"3"name:fediverse-playgroundservices:nginx:image:nginx:alpineports:- 8080:80- 8443:443depends_on:- gnusocial-alice- gnusocial-bob- mastodon-carol- mastodon-carol-streamingdb:image:postgres:alpineredis:image:redis:alpinemedia:image:darthsim/imgproxygnusocial-alice-install:build:instances/gnusocial-alice/docker/phpdepends_on:db:condition:service_healthygnusocial-alice:build:instances/gnusocial-alice/docker/phpdepends_on:db:condition:service_healthyredis:condition:service_startedmedia:condition:service_startedgnusocial-alice-install:condition:service_completed_successfullygnusocial-bob-install:build:instances/gnusocial-bob/docker/phpdepends_on:db:condition:service_healthygnusocial-bob:build:instances/gnusocial-bob/docker/phpdepends_on:db:condition:service_healthyredis:condition:service_startedmedia:condition:service_startedgnusocial-bob-install:condition:service_completed_successfullysearch:image:elasticsearch:7.17.4mastodon-carol-install:build:instances/mastodon-caroldepends_on:db:condition:service_healthyredis:condition:service_startedmastodon-carol:build:instances/mastodon-caroldepends_on:db:condition:service_healthyredis:condition:service_startedsearch:condition:service_startedmastodon-carol-install:condition:service_completed_successfullymastodon-carol-streaming:build:context:instances/mastodon-caroldockerfile:streaming/Dockerfiledepends_on:db:condition:service_healthyredis:condition:service_startedmastodon-carol-install:condition:service_completed_successfullymastodon-carol-sidekiq:build:instances/mastodon-caroldepends_on:db:condition:service_healthyredis:condition:service_startedsearch:condition:service_startedmedia:condition:service_startedmastodon-carol-install:condition:service_completed_successfullyLeft out here: volumes, environment, restart, tty, command and healthcheck.
Fediverse Playground
Drawing No.1 / 4
Scale1:1
Weight16.9KiB
Date
Projection
One bare mirror per software, one worktree per instance
An example: the shipped config puts Bob on main. Here he asks for v3, which Alice's
worktree already holds, so the branch is renamed after him.
mirrors/social-v3git clone --bare · fetch --all on every sync
instances/gnusocial-alicev3
instances/gnusocial-bobv3held by gnusocial-alice→v3-gnusocial-bob
mirrors/social-v2git clone --bare · fetch --all on every sync
instances/gnusocial-v2nightly
mirrors/mastodongit clone --bare · fetch --all on every sync
instances/mastodon-carolmain
gnusocial-bob: the first rung that matches runs
local branch already existsgitworktreeaddinstances/gnusocial-bobv3-gnusocial-bob
branch off the local headgitworktreeadd-bv3-gnusocial-bobinstances/gnusocial-bobv3
branch off origingitworktreeadd-bv3-gnusocial-bobinstances/gnusocial-boborigin/v3
start a new branchgitworktreeadd-bv3-gnusocial-bobinstances/gnusocial-bob
A Branch for Every Server
Fediverse Playground
Drawing No.2 / 4
Scale1:1
Weight16.9KiB
Date
Projection
A one-shot installer brings each server upgnusocial-alice's containers, in the order the compose file starts them
db: pg_isready every 3s
db to gnusocial-alice-install: service_healthy
gnusocial-alice-install: composer install
gnusocial-alice-install to db: doctrine:schema:create
*.localhost already resolves to 127.0.0.1, so hosts.nix leaves it out. *.fediverse needs an entry.
apply-hosts
Merges the marked block from files/hosts.snippet into /etc/hosts.
NixOS
The flake exports nixosModules.hosts, which reads files/hosts.nix.
Dev shell
Entering it checks every *.fediverse name with getent and prints one hint if one does not resolve.
Names on the Network
Fediverse Playground
Drawing No.4 / 4
Scale1:1
Weight16.9KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
LDetail LScale 1:1 ものをさがす Library search
Title
Library Search & Relevance
Medium
Rails, MariaDB
Year
Notes
One box searches every property of a library object, typed and derived alike.
MariaDB scores each hit against the best hit in the chosen category. A colour
filter only drops rows, so the rows it keeps keep their scores.
MATCH's score, over the best score the same query got
Sent asrectangular "8 seats"
Term
In
idf = log₁₀(19 / in)
idf²
rectangular
5
0.580
0.336
"8 seats"
2
0.978
0.956
Object
Σ tf × idf²
MATCH
÷ 1.292
Banquet Table White · 8 pax
1×0.336 + 1×0.956
1.292
1.00
Round Table White · 8 pax
0×0.336 + 1×0.956
0.956
0.74
Buffet Table White
1×0.336 + 0×0.956
0.336
0.26
Banquet Table White · 6 pax
1×0.336 + 0×0.956
0.336
0.26
Banquet Table White · 4 pax
1×0.336 + 0×0.956
0.336
0.26
Banquet Table White · 6 pax
1×0.336 + 0×0.956
0.336
0.26
Rank
InnoDB scores a term by how often it occurs in the row times its idf squared, and adds the terms up; a term in every row weighs nothing
Normalise
Every score is divided by the query's own maximum, so the top hit reads 1.00 whatever the raw numbers were
Numbers
One gsub quotes every number before MariaDB sees it, so 8 stops matching 81: "8" is the token 8 alone, and "8 seats" keeps a seat count as one phrase
Relevance Scoring
Library Search & Relevance
Drawing No.2 / 4
Scale1:1
Weight27.4KiB
Date
Projection
A filter narrows the relation, and the maximum comes from what is leftrectangular 8 seats → rectangular "8 seats"
No filter
Banquet Table White · 8 pax1.00
Round Table White · 8 pax0.74
Buffet Table White0.26
Banquet Table White · 6 pax0.26
Banquet Table White · 4 pax0.26
6 rows, max 1.292
+ filter
→↓
category = Catering
Buffet Table White1.00
1 rows, max 0.336
Predicate
Each filter is one more AND on the relation: the category compares a column, a property goes through library_object_synthetic_properties by name and value
Maximum
The maximum comes from the relation with its category, so the best row a category leaves reads 1.00. The controller adds the property filter after it has built the maximum, so a property can leave the top row below 1.00
Options
A property filter only offers values already stored for it. On its own, it never offers one that matches nothing
Filters
Library Search & Relevance
Drawing No.3 / 4
Scale1:1
Weight27.4KiB
Date
Projection
One match fragment, built once, doing three jobs
Bound asrectangular "8 seats"
Request?query=rectangular+8+seats0 sent · 0 dropped · 0 aborted
-- eager_load(:space_object, :properties, :styles) adds LEFT OUTER JOINs and their columns, left out here
SELECT `library_objects`.*,
((2MATCH(`library_object_properties_concatenated`.values) AGAINST('rectangular "8 seats"' IN BOOLEAN MODE))
/ (maxSELECT MAX(MATCH(`library_object_properties_concatenated`.values) AGAINST('rectangular "8 seats"' IN BOOLEAN MODE)) FROM `library_objects` INNER JOIN `library_object_properties_concatenated` ON `library_object_properties_concatenated`.`library_object_id` = `library_objects`.`id` WHERE `library_objects`.`image_processing` = FALSE AND `library_objects`.`deprecated_at` IS NULL AND (MATCH(`library_object_properties_concatenated`.values) AGAINST('rectangular "8 seats"' IN BOOLEAN MODE)) LIMIT 250)) AS search_relevance
FROM `library_objects`
INNER JOIN `library_object_properties_concatenated` ON `library_object_properties_concatenated`.`library_object_id` = `library_objects`.`id`
WHERE `library_objects`.`image_processing` = FALSE AND `library_objects`.`deprecated_at` IS NULL
AND (1MATCH(`library_object_properties_concatenated`.values) AGAINST('rectangular "8 seats"' IN BOOLEAN MODE))
ORDER BY ((3MATCH(`library_object_properties_concatenated`.values) AGAINST('rectangular "8 seats"' IN BOOLEAN MODE))
/ (maxSELECT MAX(...) ... -- the same subquery, in full, again)) DESC,
`library_objects`.`updated_at` DESC
LIMIT 250
6 rows, the subquery's MAX() = 1.292
1WHERE decides which rows come back at all
2divided by the maximum, it is the search_relevance column; Rails' #select drops library_objects.* once a MATCH is in it, so it goes on arel.projections
3the same ratio is the first ORDER BY term; updated_at, inherited from the visible scope, breaks ties
maxMAX() would fold the rows into one, so the maximum comes from the same relation reselected as a scalar subquery, category and LIMIT250 included. The property filter comes after, so it never moves the maximum
Generated SQL
Library Search & Relevance
Drawing No.4 / 4
Scale1:1
Weight27.4KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
MDetail MScale 1:1 大きさでさがす Synthetic properties
Title
Synthetic Properties
Medium
Rails, MariaDB
Year
Notes
A library object stores its size once, in centimetres. On each save, database
triggers wrote that size out in centimetres, metres, inches and feet, so a search
for 5ft3in or 1.6m found the same table. The triggers lived five weeks on a branch
before Ruby callbacks replaced them.
One table's size, split, converted and searched
The table's size is stored as 160x80x74, in centimetres. When the
row is saved, triggers split it on x, write each piece in centimetres,
metres, inches and feet, and store every form in the text that search reads.
The walkthrough types in these fields. They don't take your own typing.
1 Stored library_objects
split on x, one row per dimension
160
80
74
2 Converted library_object_synthetic_properties
cm160cm by 80cm by 74cm
m1.6m by 0.8m by 0.74m
in"63" by 31" by 29"
in63in by 31in by 29in
ft'in"5'3" by 2'7" by 2'5"
ft in5ft3in by 2ft7in by 2ft5in
Pax8
Seats8
3 Searched library_object_properties_concatenated
this table's search text
160cm by 80cm by 74cm Size (centimeters) 1.6m by 0.8m by 0.74m Size (meters) 63" by 31" by 29" Size (inches) 63in by 31in by 29in Size (inches) 5'3" by 2'7" by 2'5" Size (feet-inches) 5ft3in by 2ft7in by 2ft5in Size (feet-inches) 8 Pax 8 Seats
Synthetic Properties
Drawing No.1 / 4
Scale1:1
Weight15.9KiB
Date
Projection
Each dimension in six unitsEach row of split_sizes goes through six SELECTs, one
per unit, and GROUP_CONCAT joins a unit's pieces with ' by '.
Centimetres and metres keep two significant figures. Inches round to the whole inch.
Unit
160
80
182
Size (centimeters)ROUND(cm, 1-FLOOR(LOG10(cm)))
160cm
80cm
180cm
Size (meters)ROUND(cm /100, 3-FLOOR(LOG10(cm)))
1.6m
0.8m
1.8m
Size (inches)ROUND(cm /2.54)
63"
31"
72"
Size (inches)ROUND(cm /2.54)
63in
31in
72in
Size (feet-inches)FLOOR(in/12) + carry, ROUND(in MOD 12) ifnot0
5'3"
2'7"
6'
Size (feet-inches)FLOOR(in/12) + carry, ROUND(in MOD 12) ifnot0
5ft3in
2ft7in
6ft
182cm is 71.65in, or 5ft and 11.65in. The
11.65in rounds to 12, so it carries and prints as
6ft, not 5ft12in.
The shipped SQL did not round, so 182.5 printed as 182.5cm and
1.825m. It added a foot from six leftover inches, so 80cm read
3ft7in, and it wrote whole feet as 6ft0in. The rules above fix
all three. When rounding changes a size, the exact centimetres and metres are kept
too, so a search for 182cm still finds it.
Unit Conversions
Synthetic Properties
Drawing No.2 / 4
Scale1:1
Weight15.9KiB
Date
Projection
The same SQL in the backfill and three triggersThe backfill and the insert and update triggers on library_objects call
one Ruby method, each with its own where. The delete trigger only deletes
the rows. The hairtrigger gem writes the triggers into a migration.
app/models/library_object.rb
defself.size_synthetic_property_select_sql(where="1=1")<<~SQL.chomp
WITHRECURSIVE split_sizes AS (…) -- one row per dimensionSELECT id AS library_object_id, 'Size (centimeters)'ASname,
GROUP_CONCAT(CONCAT(ROUND(size, 1-FLOOR(LOG10(size))), 'cm') SEPARATOR ' by ') ASvalueFROM split_sizes WHERE#{where}ANDsize<>''GROUP BY id
UNION ALL-- five more, and the exact cm and m where they round SQLend
An object's rows joined into one string for FULLTEXT searchTriggers on library_object_synthetic_properties rebuild the string whenever one of the object's rows changes.
library_object_synthetic_properties
object
name
value
1024
Size (centimeters)
160cm by 80cm by 74cm
1024
Size (meters)
1.6m by 0.8m by 0.74m
1024
Size (inches)
63" by 31" by 29"
1024
Size (inches)
63in by 31in by 29in
1024
Size (feet-inches)
5'3" by 2'7" by 2'5"
1024
Size (feet-inches)
5ft3in by 2ft7in by 2ft5in
1024
Pax
8
1024
Seats
8
app/models/library_object_synthetic_property.rb · AFTER INSERT and UPDATE, each row
INSERT INTO library_object_properties_concatenated
(`library_object_id`, `values`)
SELECT
lops.library_object_id,
GROUP_CONCAT(CONCAT(lops.`value`, ' ', lops.`name`)
SEPARATOR ' ')
FROM library_object_synthetic_properties lops
WHERE lops.library_object_id = NEW.library_object_id
AND`lops`.`value`IS NOT NULLGROUP BY lops.library_object_id
ON DUPLICATE KEYUPDATE`values`=VALUES(`values`);
160cm by 80cm by 74cm Size (centimeters) 1.6m by 0.8m by 0.74m Size (meters) 63" by 31" by 29" Size (inches) 63in by 31in by 29in Size (inches) 5'3" by 2'7" by 2'5" Size (feet-inches) 5ft3in by 2ft7in by 2ft5in Size (feet-inches) 8 Pax 8 Seats
Searchable Text
Synthetic Properties
Drawing No.4 / 4
Scale1:1
Weight15.9KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
NDetail NScale 1:1 テーブルのかたち Schema driver
Title
schemaDef → Doctrine Metadata
Medium
PHP, Symfony, Doctrine
Year
Notes
GNU social is a federated social network server, written in PHP. Each of its
tables is one PHP array on the entity class, and a Doctrine driver turns that
array into the ORM mapping, the database schema and the types PHPStan checks.
GNU social is a federated social network server. Each table is a PHP array on its entity
class, and this driver turns that array into Doctrine's mapping, printed here as the SQL
it creates.
Actor::schemaDef()src/Entity/Actor.php
publicstaticfunction schemaDef(): array{return ['name'=>'actor','description'=>'local and remote users, groups and bots are actors, for instance','fields'=> ['id'=> ['type'=>'serial', 'not null'=>true, 'description'=>'unique identifier'],'nickname'=> ['type'=>'varchar', 'length'=>64, 'not null'=>true, 'description'=>'nickname or username'],'fullname'=> ['type'=>'text', 'description'=>'display name', 'default'=>null],'roles'=> ['type'=>'int', 'not null'=>true, 'description'=>'Bitmap of permissions this actor has'],'type'=> ['type'=>'int', 'not null'=>true, 'description'=>'The type of actor (person, group, bot, etc)'],'homepage'=> ['type'=>'text', 'description'=>'identifying URL', 'default'=>null],'bio'=> ['type'=>'text', 'description'=>'descriptive biography', 'default'=>null],'location'=> ['type'=>'text', 'description'=>'physical location', 'default'=>null],'lat'=> ['type'=>'numeric', 'precision'=>10, 'scale'=>7, 'description'=>'latitude', 'default'=>null],'lon'=> ['type'=>'numeric', 'precision'=>10, 'scale'=>7, 'description'=>'longitude', 'default'=>null],'location_id'=> ['type'=>'int', 'description'=>'location id if possible', 'default'=>null],'location_service'=> ['type'=>'int', 'description'=>'service used to obtain location id', 'default'=>null],'is_local'=> ['type'=>'bool', 'not null'=>true, 'description'=>'Does this actor have a LocalUser associated'],'created'=> ['type'=>'datetime', 'not null'=>true, 'default'=>'CURRENT_TIMESTAMP', 'description'=>'date this record was created'],'modified'=> ['type'=>'timestamp', 'not null'=>true, 'default'=>'CURRENT_TIMESTAMP', 'description'=>'date this record was modified'],],'primary key'=> ['id'],'indexes'=> ['actor_nickname_idx'=> ['nickname'],],'fulltext indexes'=> ['actor_fulltext_idx'=> ['nickname', 'fullname', 'location', 'bio', 'homepage'],],];}
MySQLSchemaDefDriver, printed as DBAL would
CREATETABLE actor (id INTAUTO_INCREMENTNOTNULLCOMMENT'unique identifier',nickname VARCHAR(64) NOTNULLCOMMENT'nickname or username',fullname LONGTEXTDEFAULTNULLCOMMENT'display name',roles INTNOTNULLCOMMENT'Bitmap of permissions this actor has',type INTNOTNULLCOMMENT'The type of actor (person, group, bot, etc)',homepage LONGTEXTDEFAULTNULLCOMMENT'identifying URL',bio LONGTEXTDEFAULTNULLCOMMENT'descriptive biography',location LONGTEXTDEFAULTNULLCOMMENT'physical location',lat NUMERIC(10, 7) DEFAULTNULLCOMMENT'latitude',lon NUMERIC(10, 7) DEFAULTNULLCOMMENT'longitude',location_id INTDEFAULTNULLCOMMENT'location id if possible',location_service INTDEFAULTNULLCOMMENT'service used to obtain location id',is_local TINYINT(1) NOTNULLCOMMENT'Does this actor have a LocalUser associated',created DATETIMEDEFAULTCURRENT_TIMESTAMPNOTNULLCOMMENT'date this record was created',modified DATETIMEDEFAULTCURRENT_TIMESTAMPNOTNULLCOMMENT'date this record was modified',INDEX actor_nickname_idx (nickname),-- actor_fulltext_idx: not read by SchemaDefDriverPRIMARYKEY(id)) DEFAULTCHARACTERSET utf8 COLLATE`utf8_unicode_ci`ENGINE = InnoDBCOMMENT = 'local and remote users, groups and bots are actors, for instance';
RuleHover, tap or focus a line in the PHP or the SQL to see what the driver did to it
'name''name'=>'actor'→setPrimaryTable(['name' => 'actor'])→CREATE TABLE actor
doctrine.orm.default_metadata_driverOne driver chain, one SchemaDefDriver, 74 entities as of October 2022isTransient($class)=!method_exists($class,'schemaDef')
src/Core/ModuleManager.php · process()foreach($module_pathsas$path){// … $type, $module and $dir from the pathif(!\is_null($container)&&file_exists($entity_dir=$dir.'/Entity')&&is_dir($entity_dir)){$entity_paths[]=$entity_dir;$container->findDefinition('doctrine.orm.default_metadata_driver')->addMethodCall('addDriver',[newReference('app.schemadef_driver'),"{$type}\\{$module}\\Entity"],);}}
plugins/Favourite/Entity/NoteFavourite.php is a whole plugin table: drop it in
and note_favourite exists. Its 'foreign key'=>true is still
read by nobody, since the driver's association branch sits behind if (false && …),
so note_id maps as a plain INT.
1Boot flagPHPSTAN_BOOT_KERNEL=1vendor/bin/phpstan analyseThe analyser runs with the real kernel behind it, not a stubphpstan.sh
2Real kernel$kernel->boot();return$kernel;GNUsocialProvider requires the real kernel, then starts every module as a request wouldconfig/phpstan-bootstrap.php
3Table mapDB::initTableMap();'note'=>'App\Entity\Note'Built from getAllMetadata(), so from the same schemaDefs the driver readsDB.php
4Return typenewUnionType([Note,newNullType(),])For find, getReference, getPartialReference and getRepository with a literal first argumentClassFromTableName….php
Without the flag the extension steps aside and PHPStan types the call the ordinary way,
so a plain run still works; with it, whatever is called on $note next is
checked against Note like any other typed value.
Static Analysis
schemaDef → Doctrine Metadata
Drawing No.4 / 4
Scale1:1
Weight6.6KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
ODetail OScale 1:1 いくつものげんごを読む web-ts-mode
Title
web-ts-mode
Medium
Emacs Lisp, tree-sitter
Year
Notes
An Emacs major mode for web templates such as Astro and Vue, where one file mixes
markup, TypeScript and styles. Each language gets its own tree-sitter parser over its
part of the file, and highlighting and indenting work across the whole file.
Notes
web-ts-mode is an Emacs major mode for files that mix languages. This Astro component
from the CV has TypeScript in its frontmatter, markup with expressions in it, and a
SCSS style block.
The astro parser reads the whole file, markup included. The TypeScript and the SCSS
each get a parser of their own that sees only its range. Each dashed box is one range,
drawn inside the range that holds it.
The site's build colours the code with a small lexer that follows the mode's faces.
The ranges are the ones Emacs 31.1 hands each parser for this file.
astro 1-34astro, lines 1 to 34
typescript 2-19typescript, lines 2 to 19
html 22-34html, lines 22 to 34
tsx 22tsx, line 22
tsx 24tsx, line 24
tsx 25tsx, line 25
scss 30-33scss, lines 30 to 33
1---
2/**
3 * The sheet number in the desk's left margin: a stencil numeral with that sheet's own title
4 * under it, riding along while the sheet scrolls past. It is decoration, so assistive tech
5 * never sees it; the sheet carries the same words as its heading.
6 *
7 * It is a child of #main rather than of the sheet, because the margin it stands in belongs to
8 * the desk and not to the paper. Absolutely positioned in the left margin column of the grid
9 * row its sheet sits in, it takes that row as its containing block, which is what gives the
10 * numeral the sheet's own height to stick along. The row is the number: there is one folio
11 * per sheet and they run in page order. Both ends of the row are named, since a grid line an
12 * absolutely positioned child leaves at auto resolves to the grid's own padding edge.
Point at or tab to the code to see which parser owns itastro: the host parser, over the whole buffertypescript: frontmatter (frontmatter_js_block), :local ttsx: attribute_interpolation (attribute_js_expr), :local ttsx: element (html_interpolation), :offset '(1 . -1), :local ttsx: element (html_interpolation), :offset '(1 . -1), :local tscss: style_element (raw_text), picked by lang="scss", :local t
In a>b the right side starts one level past the deepest on its left.
The arguments of :not() and :has() sit one level deeper than the pseudo-class.
The branches of a,b share a depth. A nested rule starts again at 0.
Selector Depth Hue
web-ts-mode
Drawing No.3 / 4
Scale1:1
Weight145.3KiB
Date
Projection
One command says what is installed and what is not
Notes
Each language needs a tree-sitter grammar, and completion and error checks need a
language server on PATH. M-x web-ts-doctor lists both.
A missing grammar shows up as a line here. When a file opens, the mode leaves that
language unhighlighted and carries on.
This is the report from the machine that builds this CV. Every grammar is installed,
and one server, astro-ls.
web-ts-mode doctor=================Emacs: 31.1System type: gnu/linuxGrammars-------- astro: ok css: ok tsx: ok typescript: ok javascript: ok html: ok scss: ok xml: ok jsdoc: ok vue: okLSP--- client: eglot auto-enable: tLanguage servers on PATH------------------------ astro-ls: found (node_modules/.bin/astro-ls --stdio) vue: not found (tried vue-language-server, vls) html: not found (tried vscode-html-language-server, html-language-server, vscode-html-languageserver, html-languageserver) xml: not found (tried lemminx, xml-language-server) typescript: not found (tried typescript-language-server) css: not found (tried vscode-css-language-server, css-languageserver, vscode-css-languageserver)CLI--- bin/web-ts grammars — install pinned tree-sitter grammars bin/web-ts doctor — this report (batch) bin/web-ts test — ERT suite bin/web-ts compile — byte-compile package .el files bin/web-ts bench — soft perf timings
U:%%- *web-ts-doctor* Top (Special Follow)U:%%- *web-ts-doctor* (Special Follow)
Doctor Report
web-ts-mode
Drawing No.4 / 4
Scale1:1
Weight145.3KiB
Date
Projection
threemoresheets,peelthiscorner
thiscornerdragsthepageback
03Career
SH-CV/REV A
しごとと学校 Career
しごと Experience
RevPeriodDescription
06
to present
Backend web developerat Runtime Revolution
Backend development in Ruby on Rails at Runtime Revolution, a Lisbon company that builds software for its clients
05
to
Full-stack web developerat Visrez, Ireland, remote
A Vue and Three.js app that lays an event space out in 3D. Blocks of tables and chairs fill a drawn area and guests get seats.
A React map of a resort with custom pins, a virtual tour and step-by-step wayfinding.
The Ruby backend behind both. 3D model orders, subscriptions, Google and Apple sign-in and a library of about 11,000 3D objects.
A startup enabling researchers to design and run protein folding simulations without requiring coding expertise. A node-based editor in Vue and TypeScript wires up inputs, algorithms, outputs and plots, and the app writes the Julia that runs them
03
to
Core maintainerat GNU social
The v3 rewrite of the federated social network, in PHP. An event-driven modular architecture, low system requirements, and a deployment a sysadmin can run without much time for it
Brought an acquired ride-sharing platform's PHP backend up to date and its iOS and Android apps onto the versions then current, then built a driver and vehicle management platform with a React Native app
01
to
Table service and catering
Summer jobs waiting tables and working catering
学校Education
BSc Computer ScienceMinor in MathematicsFaculdade de Ciências, Universidade do Porto, Coding since age 13, always learning
ことばLanguages
PortugueseNative
EnglishC2
SpanishC1
JapaneseA1
ばしょLocation
Porto, Portugal
すきなことInterests
Open Source, Rubik's cubes, languages, motorcycles, the gym, cooking and bread
FreeCAD on NixOS crashed on a boolean subtract of a cube and an inscribed cylinder. The crash led through OpenCascade 7.9.3's point classifier to a wrong-code bug in GCC 16, where interprocedural constant propagation redirected a recursive call to a clone specialised for a different dynamic type, so the program ran the wrong virtual method. Bisected GCC to two January 2026 changes, reduced the miscompile to a C++ testcase of a few dozen lines, wrote the fix and a regression test, and bootstrapped and regtested GCC. The IPA maintainer reviewed it and pushed it to master the same day, with a GCC 16 backport planned. Around it, a nixpkgs workaround builds OpenCascade with `-fno-ipa-cp-clone` and fails the build if the miscompile returns, an OpenCascade issue asks for a 7.9 backport of its fix, and a FreeCAD PR backports OpenCascade 8 support to the 1.1 branch
メンテナー Maintainer
GNU social v3
GNUsocial/gnu-social
Commits
~1200
Period
2021–2022
Led the GNU social v3 rewrite from 2021 to 2022 with ~1200 commits, funded by NLnet through the NGI0 Discovery programme. Ported the federated microblogging stack to Symfony 5 with an event driven and modular architecture. Rewrote the controllers and posting form layer, expanded PHPUnit coverage, and integrated PHPStan and tooling for dependency hygiene
GSoCの学生 GSoC student
Blender GSoC 2019
blender/blender
Period
2019
Proposed replacing Blender's Python OBJ, STL, and PLY importers/exporters with a shared C/C++ IO framework using memory-mapped files and optional parallelization. A prototype patch made OBJ and STL import and export about 3× faster, and the proposal was accepted into the GSoC programme
Added the ELAN 04f3:0c6e press sensor of the ASUS ROG Flow X13 to libfprint's new elanpress driver. On master it sits under the swipe-only elan driver, where enrolment fails with a protocol error and verify never matches. Two follow-up fixes keep a background frame captured with the finger down from becoming a touch, and keep more distinct probe frames per touch. Scored offline on a recorded session, 15 held-out touches of the enrolled finger matched at 0.848 to 0.954 and 47 touches from other fingers and another person stayed at 0.294 to 0.761, so no false accepts. inauman took all three commits into the branch behind libfprint!654 on 2026-10-20 and closed this one, so they now wait on upstream review there
Biome's HTML lexer read the ? before > as part of a name, so <?xml version="1.0"?> never closed and astro check rejected an SVG component that starts with an XML declaration (withastro/compiler-rs#199). Opened #12159 with the fix and fixtures for the plain, Astro, Vue and Angular lexers. The maintainer landed a reworked version with me as co-author
FreeCAD booleans crashed on NixOS because OpenCascade's bounding box tree walk ran the line selector's Accept on the point selector. GCC 16's IPA-CP had redirected a recursive call to a clone made for a different polymorphic context. Bisected it with prebuilt snapshots to two January changes, reduced it to two short C++ testcases, wrote the fix and a regression test, and bootstrapped and regtested on x86_64. The IPA maintainer reviewed it and pushed it to master the same day, with a GCC 16 backport to follow (bug 127917). A nixpkgs PR builds OCCT with -fno-ipa-cp-clone and adds an install check for the miscompile, an OpenCascade issue asks for the OCCT 8 fix to be backported to 7.9, and a FreeCAD PR backports its OpenCascade 8 support to the 1.1 branch
Reworked the scope_css pass in Astro's compiler_rs so combinators inside :global() hoist correctly, dropping the broken ParserOptions::css_modules path
Every FP_ELANPRESS_* setting comes from the environment, and some bad values broke the driver instead of being refused: a nan quality gate rejected nothing, a huge FP_ELANPRESS_NORM_WIN hit an undefined int cast, and a tiny FP_ELANPRESS_ROT_STEP overflowed the count of rotated probes. Five commits make the shared parser ignore values that are not finite, bound the integer, rotation and overlap settings, and log what it ignored or clamped. Each has a unit test that fails without it. Stacked on !3, awaiting review
The quality gate drops frames under half the best contrast of a touch, but it took that best from every frame, including ones the absolute gates reject. A frame from a finger landing or lifting is mostly ridge edges and can have twice the contrast of a full press, and on 0c6e one such frame put the bar above all eight full-contact frames of its touch. The best now comes only from frames that pass the absolute gates, with a unit test that feeds that touch. Replaying 116 touches from daily use, six keep more probe frames and the rest are unchanged. Awaiting review on the branch behind libfprint!654
Drivers that take a setting from an environment variable parsed it by hand with g_ascii_strtoll(), so 5x read as 5, abc as 0, and an out-of-range value was dropped without a message. fpi_env_get_int64() and fpi_env_get_double() take the accepted range and a default, and log anything malformed or out of range along with the default they return instead. The virtual devices' HOT_SECONDS settings and mafpmoc's MAFP_ENROLL_SAMPLES move to them, and the elanpress driver in libfprint!654 can follow. Awaiting review
Sashiko runs claude with --tools "", and when a review stage asked for tool calls, Opus 5.5 sometimes wrote them out as plain text, then kept inventing calls and results until the 600 s timeout killed it. Requests with tools now also pass --json-schema, so the turn ends on one structured reply, and the model still has no files, commands or network. A drm/client kernel series that ran for about 7 hours and failed now gets a full review in 13 minutes. Fixes #737, awaiting review
On 0c6e a rejected press against 80 enrolled images took about a second on 16 threads, nearly all of it in the NCC inner loop. Five commits rework that loop: 16-bit layers so every sum is one multiply-add, summed-area tables for the single-layer sums, a half-resolution search kept to the best quarter-resolution peaks, SSE2 intrinsics, and AVX2 and AVX-512 copies picked at run time. Matching 3 probe frames against 80 images on one core went from 2.1 s to 0.17 s. Every commit but the peak search gives the same scores on 240 genuine and 3136 impostor matches. Awaiting review
On an ASUS ROG Flow X13 booted with video=eDP-1:1920x1200@60, an HDMI hotplug made fbdev reprobe every connector with its 1920x1200 framebuffer as the limit. The 3840x2400 eDP panel lost its native mode from connector->modes, and after suspend/resume amdgpu drove it at 1920x1200 with the image squeezed into the top-left corner. Patch 1 makes drm_client_modeset_probe() probe with the device's mode size limits, as fbdev's first probe already does, and moves the framebuffer size check into the fbdev mode pickers. Patch 2 adds KUnit tests. v2 also checks the size of the CRTC's current mode before the firmware config falls back to it, which Sashiko found. With only this series applied, 255 of 255 resumes kept the native timing, against 28 of 29 wrong without it. Ville Syrjälä said on 2026-10-16 the approach should be fine but gave no tag, so it is still under review
Two patches on amd-gfx. After an HDMI hotplug and a suspend/resume, a 3840x2400 eDP panel booted with a video= mode came back squeezed into the top-left corner. The fbdev reprobe had dropped the native mode and amdgpu scaled onto the first one left. On eDP and LVDS the fix falls back to a listed mode of the native size, or else to the encoder's native mode, with KUnit tests that fail without it. On 7.2.8, 103 of 103 resumes were wrong before and 1363 of 1363 right after. v2 is rebased onto amd-staging-drm-next, as Tom Chung at AMD asked. A second series on dri-devel, drm/client: Keep larger modes in connector->modes on fbdev hotplug, fixes the same bug for every driver. Both are under review
When some tiles of a tiled display are missing, drm_client_firmware_config() swaps in drm_connector_fallback_non_tiled_mode(), which can return NULL, and the debug message after it still read modes[i]->name. It now prints (none) there, and drops the stray colon in [CONNECTOR::%d:%s] that broke grepping the log for a connector id. Sashiko found it while reviewing the fbdev hotplug series. Thomas Zimmermann suggested the v2 form and gave his Reviewed-by on 2026-10-23
The evdev listener slept 5 ms between passes over its devices, so an idle daemon woke about 200 times a second to find nothing to read. It now blocks in poll() on the keyboard fds and the inotify watch, with a 100 ms timeout so the stop signal and the device checks still run, and handles key events as they arrive. On my laptop an idle daemon went from about 229 wakeups a second to about 42. A draft, waiting on CI approval and review
The action picks the panel's ABM level from the profile and the battery charge, with power-saver at 0 above 50% and balanced at 1 below 30%, and nobody could change it: one user wanted the saving at any charge (#185), others found level 3 too strong (#159). Adds a load_settings vfunc so an action reads its own group from power-profiles-daemon.conf, with PowerSaverLevel, BalancedLevel and PerformanceLevel keys that apply on battery at any charge; unset keys keep today's steps, and unknown groups, keys or values out of 0 to 4 get a warning. A first commit fixes a leak already on main, where both error returns in set_panel_power() skipped freeing the drm device list. Measured on an ASUS ROG Flow X13: level 4 saves 0.4 to 0.6 W of about 10.4 W. Stacked on !241
power-profiles-daemon saved an action as off whenever it was inactive for one run, so a --block-action debug flag or a failed probe switched it off for good in state.ini, and a configure-action choice was lost if the daemon stopped before the next profile change. The first commit keeps the state file to the user's own choices instead of what is active right now; the second writes it as soon as configure-action or configure-battery-aware runs. Seven new integration tests cover the blocked, opt-in and failed-probe cases and the restarts; all 152 pass
GCC 16 at -O3 miscompiles OCCT 7.9.3. Classifying a point on an edge or vertex of a solid segfaults, and so do booleans where faces touch along a line, which is how FreeCAD crashed on a boolean subtract of a cube and an inscribed cylinder. Builds OCCT with -fno-ipa-cp-clone when the compiler is GCC 16 or newer and adds an install check that runs the failing classification, so the build fails if the miscompile comes back
Blocking a driver or an action meant overriding ExecStart in a systemd drop-in, which breaks when a distribution installs the daemon elsewhere or changes the unit. The daemon now looks for power-profiles-daemon.conf and a .conf.d/ directory in /etc, /run, /usr/local/lib and /usr/lib, in systemd's order and with systemd's rules: a later key replaces an earlier one, a drop-in of the same name higher up masks the lower one, a link to /dev/null masks a file, and files are opened without blocking and read rather than mapped so a FIFO or a truncated file cannot stop startup. The [Daemon] group takes BlockDrivers and BlockActions, and unknown keys, groups and names get a warning. A second commit makes SetActionEnabled refuse to turn on a blocked action instead of reporting success
Tapping a tab on a touchscreen did not switch to it, and the tab bar could disappear until a new window opened, because the click handler only listened to mouse buttons, which Hyprland never raises for touch. Moves the lookup into Hy3Layout::tabAt() and calls it from an input.touch.down listener too; a tap on a tab cancels its down, motion and up events so the client underneath never sees the touch, and the listener repeats Hyprland's own checks for the locked session, kill mode, workspace swipes and covering surfaces. Tested in a VM with a virtual touchscreen and earlier on a real one
Unloading hy3 with a tab group open crashed Hyprland on the next animation tick, which any reload that changes the plugin path triggers, such as a NixOS switch that rebuilds hy3. Hyprland calls PLUGIN_EXIT first and removes the layouts after, and removing them put fading tab groups back on a list whose tick listener was already gone. Removes the layout at the start of PLUGIN_EXIT so the clears free what is left, and drops the tab pass element from the render pass as hyprbars does. Tested in a VM through an unload, a reload and six swaps between builds
When the last monitor disconnects, Hyprland leaves workspaces pointing at the monitor it is destroying, and hy3 dereferenced that pointer on the next window map or unmap and segfaulted. Checks the workspace's monitor in insertNode, recalcGeometry, moveNodeToWorkspace and the tab group constructor, and the focused monitor in the dispatchers and cursor warp, which is null while no monitor is connected; each place skips its monitor work. Fixes #332 and #336
Letting go of push-to-talk a moment early cut the last word, and Whisper dropped it or guessed. With audio.stop_tail_ms set, the daemon keeps capturing after the stop until the last 160 ms are quiet, for at most that long; a clean release stops at once. Quiet is three times the recording's noise floor, taken as its 10th-percentile frame and capped where quiet speech starts, so the bar follows the mic and the room. Off by default, with a schema entry, a field in the configure TUI, docs and a unit test
The modifier guard opened every /dev/input/event* node on each paste and closed them all afterwards, and each close waits for a kernel RCU grace period of 10 to 35 ms, so the guard alone added a few hundred milliseconds before every output (#543). Keyboards now stay open in a process-wide cache; each call still lists /dev/input and opens only nodes that are new or re-created, told apart by inode, so hotplug keeps working, and any close runs on a background thread off the output path
With context window optimisation on, Whisper sometimes reaches the end of the speech and transcribes the clip again from the start, returning the transcript twice or with its opening sentence repeated at the end; the loop check from #827 needs three copies, so these got through. If the first pass used a reduced audio_ctx and the text ends with a copy of its first six or more words, the text before the copy is kept, with no second pass. Replaying 20 archived dictations on large-v3-turbo, the three that came back doubled now match the full-context output at no extra cost. Stacked on #827
Kept the microphone open while a push-to-talk dictation transcribes in Voxtype. A press in that window is queued and recorded on the open capture, so the next sentence is no longer lost
Added hotkey.cancel_on_other_key for a modifier used as the hotkey. Pressing any other key while it is held cancels the recording, so Right Ctrl+C no longer dictates
Added hotkey.double_tap_latch. A double tap keeps the recording running after the key is released, until the next press, and holding the key works as before. A small TapLatch state machine in the evdev listener does the timing
Added [hotkey.language_modifiers], which maps modifier keys to Whisper language codes. Holding one during a recording transcribes that recording in that language, and the next goes back to the configured one
Added a loop check to the Whisper transcriber. When a phrase of three or more words repeats three or more times in a row, it retries with beam search, and keeps one copy of each phrase if the retry loops too
Fixed picking in the 3D view on fractional-scale screens. toDevicePixelPosition() flipped the Y axis in logical pixels and only then scaled to device pixels, so on a 2.67× panel a click landed 1 to 4 device pixels below the cursor
Added WISPR_CAPTURE_IGNORE_VK, a list of virtual-key codes the Linux helper leaves out of its global key monitor, so a push-to-talk bind on Right Ctrl stops firing on Left Ctrl as well. Skipped keys do not take a sequence index, so the app never sees a gap, and paste injection is untouched
Added on-enter and on-leave command hooks to every Waybar module, requested since 2023. Ignored GTK's inferior crossings and latched on a hover flag so each command ran once per hover, and mirrored the on-click guard that keeps built-in actions from also running as shell commands
Packaged claude-auto-retry as a Nix flake with NixOS and home-manager modules that build from source and manage the shell wrapper, the reconcile timer, and the StopFailure hook through real options instead of npm i -g. Also fixed the wrapper clobbering each tmux pane's TERM
With the Biome lexer fix, <?xml version="1.0"?> parses as an HtmlProcessingInstruction in Astro files, and astro2tsx copied it into the TSX verbatim, where TypeScript cannot parse <?. It now prints each instruction as a JSX comment through the path HTML comments use, at the root before the doctype and inside elements. New snapshot fixtures and a type-check test cover it, and astro check on the repro went from 7 errors to 0. Closes #199. Closed in favour of #205, which the maintainer merged with the same astro2tsx change
Backported the OpenCascade 8 support from main to the 1.1 release branch, 15 commits plus the -Werror guards and the TechDraw fix, so distros shipping FreeCAD 1.1 could build against OCCT 8 and sidestep the GCC 16 miscompile of 7.9.3. Tested with FreeCADCmd -t 0 on OCCT 8.0.1 and 7.9.3, 1666 tests with the same results as 1.1.4, and the cube minus cylinder recomputes instead of crashing. The maintainers closed it, since the 1.1 branch gets no further releases and 26.3 is the first version to support OCCT 8
In paste mode the output chain held only the paste method. When a modifier was still down past modifier_release_timeout_ms, the guard skipped the paste and nothing else ran, so the dictation was lost although the notification said it was copied to the clipboard. Hit with Right Ctrl as the hotkey. Added wl-copy after the paste, as type mode already has, with a test that the chain keeps a method that survives the keystroke filter. Closed as a duplicate of #588 and #654
Added git rebase --continue --no-edit and rebase.noEdit to skip opening $EDITOR after resolving conflicts, with --edit to override the config for a single continue. Junio Hamano and Phillip Wood worried that rebase.noEdit would also skip the editor after an edit or reword stop, and asked for tests beyond the conflict case. Dropped without a v2
Wrapped every Intl stub class in a class_exists guard, so loading the polyfill next to a real intl extension stops fataling with "Cannot declare class Normalizer, because the name is already in use"
Bumped gtk4 to 4.22.5 for the upstream munmap() fix in the Wayland dmabuf format table, which took down any GTK4 client on Hyprland whenever a monitor was plugged in. Traced from a coredump and first sent as a backported patch; the reviewer asked for the bump once 4.22.5 was tagged. Closed as a duplicate of #559510, the GNOME 51 update on staging, which takes gtk4 to 4.24.0
Upgraded cte-builder to Doctrine 3 and beberlei/assert 3.3. Closed after the maintainer folded parts of it into 3.1.0, which supports DBAL 2 and 3 side by side
Packaged php-align for MELPA with a recipe covering its PHP alignment rules. Closed once the thread led to php-align.el being merged into php-mode instead
Lets git mv create missing leading directories in the destination instead of failing: git mv -p foo bar/quux/ creates bar/ and bar/quux/ first, then moves foo into place