Describe the 500 weight and the SVG icons (#214)

- Architecture: fonts load 400, 500, 600 and 700. Icons, markIcons and
  the chevron are described.
- Development: tests find icons through __gm.locateIcons(id).
- Behaviors, Scenes, Sounds: kind, scene and sound marks are described
  instead of written as glyphs.
- Development: editor-suite check sum 1312.
admin committed 2026-09-23 08:13:41 +02:00
1 parent db8b6428d3
commit fa9e6a5540
5 files changed
+28 -11

No files matched your search

+10 -4
@@ -59,9 +59,10 @@
`labelToken()` and `measureToken()`, which a subclass overrides (`TabButton` draws and measures
plain). The box behind the label is `drawBackground()`, likewise overridable: `TabButton` paints
an index card there, rounded above and square below (#176).
The padding is an inset rather than a claim, so a 24 px icon button spends it on the glyph
instead of truncating it away; a button that switches between label forms (`↺` and `↺ ▾`) asks
for `minWidth` and grows. `__gm.textOverflows()` guards the whole class of bug: it reports any
The padding is an inset rather than a claim, so a 24 px icon button spends it on the icon
instead of truncating it away; a row action that switches between its plain and its menu form
(the reset picture alone, or with a chevron) asks for `minWidth` and grows by the chevron's room
(`trailingRoom()`, which `Dropdown` uses for its chevron too). `__gm.textOverflows()` guards the whole class of bug: it reports any
visible text drawn wider than the widget owning it, `__gm.labelsDropped()` guards the other
direction — a button too narrow to draw any of its label (e2e step 14).
- **Vite + TypeScript**, **Vitest** for core, toolkit and editor logic.
@@ -106,7 +107,10 @@ src/runtime/physics/ engine (loads Rapier once, reports its state, converts be
normal, impulse and point; whether a dynamic body lands on or passes a one-way collider,
decided before each step for the contact hook; the push a `Move` gives a dynamic body, and
what the physics nodes reach through `NodeContext.physics`)
src/ui/ widget toolkit (generic, knows nothing about assets); SortableList + sortable (the layer list of every editor that has one: rows dragged into order, eye, duplicate, cross, rename in place; the index arithmetic of a list shown upside down, kept apart so it is measured without a canvas)
src/ui/ widget toolkit (generic, knows nothing about assets); fonts (the faces the editor ships:
Source Sans 3 in 400, 500, 600 and 700, Source Code Pro 400, Latin only, #202/#214), icons +
Icon (the menu's tick boxes and current-entry dot as SVG files, and a widget that shows one file
on its own: a symbol the Latin subset lacks is never written as a character, #214); SortableList + sortable (the layer list of every editor that has one: rows dragged into order, eye, duplicate, cross, rename in place; the index arithmetic of a list shown upside down, kept apart so it is measured without a canvas)
src/viewport/ view (transforms), items (what to draw), symbols (how to draw it),
symbolIcons (the SVG files in icons/, parsed once, in the theme's colours, #175),
FileLibrary (load once, never retry a failure, tell the listeners) ← TextureLibrary, FontLibrary;
@@ -169,6 +173,8 @@ src/editor/panels/ AssetPanel, assetGroups (groups by type and their order,
assetMenu (the two groups of the + menu), HierarchyPanel,
Toolbar + toolbarIcons (five icon buttons, no captions: menu, undo, redo, play, pause, #183),
headerIcons (the pictures of the tab headers' switches: the magnet, #194),
markIcons (the kind marks, the row actions reset, apply and the two fit arrows, and a
transition's in/out arrow — they were characters the bundled font does not have, #214),
TilemapPanel (tools, tiles and layers of the one selected tilemap, beside the scene view, #142;
painting itself is SceneViewport)
src/editor/graph/ GraphEditor (header, breadcrumb), GraphCanvas, graphView (zoom limits, grid, panAndZoom — shared with NodeBoard and the Tile tab's TileCanvas), EdgeLayer (curves and fold marks), graphMenus,
+3 -2
@@ -52,8 +52,9 @@ shown on the node and editable in the inspector.
because no tab may reach into the `Editor` to switch itself on. An **unconnected `list` input**
of a node is the same button and the same tab: its inline value lives in the very same params
bag a setting does, and the slot stops resolving the moment an edge feeds the port. What an item holds
is **its own** and is chosen there: the **kind mark** in front of a row (`#`, `Aa`, `✓`, `◧`, `▦`, `⊞`)
is the picker — a click opens the menu, with no chevron, since the mark has to stay as narrow as
is **its own** and is chosen there: the **kind mark** in front of a row (`#` for a number, `Aa` for a text, a
small picture for every other kind: a tick for a boolean, a half-filled square for a color, a grid
for a texture, a square with a cross for a struct) is the picker — a click opens the menu, with no chevron, since the mark has to stay as narrow as
its column — and `+ Item ▾` asks for the kind of the one it appends. Because the kind lives in the
value, every such change is an ordinary **value edit**: one command, one undo step, and no
neighbouring item touched. A **struct item** carries its own shape the same way: its fields are
+11 -1
@@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
php tools/make-user.php <name> <password> # an account; every tool signs in (see Accounts)
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1310 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1312 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
@@ -121,6 +121,16 @@ the whole answer there, and no colour check can see an order.
The node palette has the same property, and `__gm.graph.paletteRows()` is its witness: the rows all
look alike, only their sequence says whether the search ranked them (#43).
**A symbol is a picture, so a step finds it as one** (#214). The reset and apply buttons, the fit
arrows, the kind marks, the tick boxes and the scene marks used to be characters, and `locate`
found them by that character. They are SVG files now, because the Latin font the editor ships has none of those glyphs.
A file that carries an `id` names its graphics `icon:<id>`, and `__gm.locateIcons(id)` lists every
such picture on screen as `{ x, y, menu }`: `menu` says whether the button it sits on opens a menu.
In a step, `t.findIcon(id, menu)` and `t.clickIcon(id, menu)` do the same as `find` and `clickText`.
A menu's tick boxes are read through `t.menuTicked()` as `{ label, ticked }`. The unit test
`tests/ui/fonts.test.ts` reads the character map straight out of the font files and fails on any
character in `src/` that one of the shipped faces lacks.
**What no colour check can see: whether anything was drawn at all.** `__gm.pixelAt` re-renders the
stage on demand, so it answers correctly on a screen that has been frozen for a minute — which is
exactly the failure mode of drawing on change instead of every frame (see *Architecture*). The
+3 -3
@@ -6,13 +6,13 @@ Those are three different questions, and this page is about keeping them apart.
The **active scene** is what the editor shows: the Scene tab draws it, the hierarchy lists it, a
dropped asset lands in it. It is set by double-clicking a scene in the Assets panel, or through
*Set as active scene* in its context menu — the toolbar has no scene picker since #183. `●` marks
*Set as active scene* in its context menu — the toolbar has no scene picker since #183. A dot marks
it in the list. Both ways stay open while a run is on: they only move the editor's view and never
touch the running world.
The **start scene** is where a run begins — the title screen, the first level, whatever a player
would see first. It is named on the **project**, not on the scene asset: right-click a scene in
the Assets panel → **Set as start scene**. `▶` marks it, and the same place offers **Clear start
the Assets panel → **Set as start scene**. The play triangle marks it, and the same place offers **Clear start
scene** to take the name back. A project that never names one has none, and then the two questions
have the same answer.
@@ -29,7 +29,7 @@ It stands in the settings dialog beside the project name and the collision layer
belong to the project — the dialog is the one place these are edited, not the split it says.
It is captioned with the mode it stands in — *Start scene* or *Active scene*, worded exactly like
the entries of its own menu, so that what you picked and what you read back are the same words.
The start scene is marked `▶` in the Assets panel, the active one `●`.
The start scene is marked with the play triangle in the Assets panel, the active one with a dot.
The point of the two modes is that a game with a title screen is otherwise tedious to work on:
every run would begin at the title, three clicks away from the level you are editing. *Active
+1 -1
@@ -9,7 +9,7 @@ file that fails does not stop the rest. The asset is named after the file ("jump
an `.m4a` as `audio/mp4`, `audio/x-m4a` or as nothing at all.
**+ → Sound** asks the other question and opens the picker: **every sound the server already
holds**, one row each. A sound has nothing to show, so the row carries a **▶** at its left that
holds**, one row each. A sound has nothing to show, so the row carries a **play triangle** at its left that
plays it — pressing that is a preview and picks nothing. Picking a row and pressing **Use sound**
makes the asset; **Upload…** is the way in for a file the server does not have yet. A row
captioned with an asset name is one this project already uses, and picking it uses that asset