@zerwiz/ymir 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.agents/skills/galdr-ymirsystem/assets/installation.md +122 -0
  2. package/.agents/skills/groa-update/SKILL.md +9 -0
  3. package/.agents/skills/ymir-host/assets/install.md +29 -0
  4. package/CHANGELOG.md +119 -0
  5. package/README.md +54 -0
  6. package/bin/design-icon.sh +18 -3
  7. package/bin/desktop-place.sh +20 -5
  8. package/bin/ymir-config.sh +76 -0
  9. package/bin/ymir-install.sh +16 -3
  10. package/bin/ymir-style.sh +24 -0
  11. package/bin/ymir.js +35 -0
  12. package/midgard/README.md +16 -0
  13. package/midgard/design-system/.gitkeep +0 -0
  14. package/midgard/design-system/ember.d.ts +13 -0
  15. package/midgard/design-system/ember.js +183 -0
  16. package/midgard/design-system/icons/algiz.svg +4 -0
  17. package/midgard/design-system/icons/ansuz.svg +4 -0
  18. package/midgard/design-system/icons/berkana.svg +4 -0
  19. package/midgard/design-system/icons/dagaz.svg +4 -0
  20. package/midgard/design-system/icons/ehwaz.svg +4 -0
  21. package/midgard/design-system/icons/fehu.svg +4 -0
  22. package/midgard/design-system/icons/gjallarhorn.svg +4 -0
  23. package/midgard/design-system/icons/gungnir.svg +4 -0
  24. package/midgard/design-system/icons/heimdall.svg +4 -0
  25. package/midgard/design-system/icons/ingwaz.svg +4 -0
  26. package/midgard/design-system/icons/jera.svg +4 -0
  27. package/midgard/design-system/icons/kaunan.svg +4 -0
  28. package/midgard/design-system/icons/midgard.svg +4 -0
  29. package/midgard/design-system/icons/mimirsbrunn.svg +4 -0
  30. package/midgard/design-system/icons/othala.svg +4 -0
  31. package/midgard/design-system/icons/raidho.svg +4 -0
  32. package/midgard/design-system/icons/ratatoskr.svg +4 -0
  33. package/midgard/design-system/icons/sowilo.svg +4 -0
  34. package/midgard/design-system/icons/tiwaz.svg +4 -0
  35. package/midgard/design-system/icons/utgard.svg +4 -0
  36. package/midgard/design-system/icons/valhalla.svg +4 -0
  37. package/midgard/design-system/icons/yggdrasil.svg +4 -0
  38. package/midgard/design-system/icons.md +59 -0
  39. package/midgard/design-system/runes.md +78 -0
  40. package/midgard/design-system/tokens.css +109 -0
  41. package/midgard/design-system/ymir-mark.svg +62 -0
  42. package/package.json +2 -1
  43. package/scripts/electron.sh +18 -2
  44. package/scripts/start.sh +66 -12
@@ -192,6 +192,128 @@ cloth_rules[4]{rule,why}:
192
192
  TOON stays on stdout. The installer prints it at the consent, and ends with the
193
193
  next steps — *a reaction for every action, a next step for every ending*.
194
194
 
195
+ ### A clone and a package are one tree — `bin/app-lib.sh`
196
+
197
+ The apps are the only thing that differs between the two shapes, and only in
198
+ where they live:
199
+
200
+ ```
201
+ surface a clone an npm install
202
+ --------------- ---------------------- ------------------------------------
203
+ hlidskjalf apps/hlidskjalf node_modules/@zerwiz/hlidskjalf
204
+ hlidskjalf-mobile apps/hlidskjalf-mobile node_modules/@zerwiz/hlidskjalf-mobile
205
+ odrerir apps/odrerir node_modules/@zerwiz/odrerir
206
+ sessrumnir apps/sessrumnir node_modules/@zerwiz/sessrumnir
207
+ smidja apps/smidja-factory node_modules/@zerwiz/smidja-factory
208
+ ```
209
+
210
+ `bin/app-lib.sh` answers for both — `app_dir <surface> <var>`, `app_pkg
211
+ <surface>` — and **eighteen files** were converted to it: the raise path
212
+ (`scripts/start.sh`), the windows (`scripts/electron.sh`), the invite door, the
213
+ seat-hall trio, Eir, the icon mint, the desktop placement, the hall snapshot, and
214
+ the installer's own SPA and shell steps. `bin/smidja-lib.sh` delegates to it, so
215
+ there is **one** truth about where things live.
216
+
217
+ **The trap that named itself.** `printf -v <name>` writes to the *function's*
218
+ scope. A helper whose scratch variable shares the caller's requested name
219
+ swallows the answer — `app_dir hlidskjalf c` returned nothing because the
220
+ helper's own `local c` held the path. Every scratch name in the resolvers is
221
+ function-prefixed (`_apd_c`, `_smd_c`, `_apr_root`) for exactly that reason, and a
222
+ new resolver must follow the rule.
223
+
224
+ **Why it matters beyond tidiness:** a packaged install shipped with eighteen
225
+ scripts looking in `apps/`, so `ymir raise` died on
226
+ `cd …/apps/hlidskjalf: No such file or directory` while every package had in fact
227
+ arrived. A shape assumption is a bug that only shows itself on the *other* shape.
228
+
229
+ ### The hall answers on the port it was given
230
+
231
+ `scripts/start.sh` raises the SPA, and the SPA must listen on
232
+ `$HLIDSKJALF_PORT` (3888 by default) — every other door looks for it there. Two
233
+ rules came out of a packaged install where it listened nowhere:
234
+
235
+ ```
236
+ spa_serving[2]{shape,how}:
237
+ "a packaged install","serves its built ./dist with `vite preview --port $PORT --strictPort`"
238
+ "a clone","serves the dev server, but STILL on $PORT — `npm run dev -- --port $PORT --strictPort`"
239
+ ```
240
+
241
+ A bare `npm run dev` lets Vite take 5173 and the hall silently answers on the
242
+ wrong port; the raise now prints the log's last lines when the port stays silent
243
+ rather than claiming success. Óðrerir resolves through `bin/app-lib.sh` like every
244
+ other surface (it read as `missing apps/odrerir` on a packaged install).
245
+
246
+ ### The marks land on any desktop — not only Omarchy's
247
+
248
+ `bin/desktop-place.sh` holds **two kinds of thing**, and they were behind one gate:
249
+
250
+ ```
251
+ desktop_halves[2]{half,who_reads_it}:
252
+ "the window rules (numbered desktops, the Lua rule)","Hyprland / Omarchy only"
253
+ "the launcher entries and the rune icons","the freedesktop standard — GNOME, KDE, Hyprland alike"
254
+ ```
255
+
256
+ A GNOME operator gets entries and icons and no window rules, which is the correct
257
+ answer — and the reason a packaged install on GNOME placed *nothing* while the
258
+ clone's marks already existed. `bin/desktop-place.sh entries` is the launcher half
259
+ alone (any Linux desktop), the installer's `marks` step calls it, and both the
260
+ desktop database and the icon cache are refreshed after.
261
+
262
+ ### Every notice can be silenced — `ymir config`
263
+
264
+ Some lines are information the first time and noise the tenth. A user must be able
265
+ to say **not again**, once, and be believed. The preferences live in the operator's
266
+ settings (`<home>/config/notices.conf`, one `key=on|off` per name) and the door is:
267
+
268
+ ```
269
+ ymir config # what is shown, and what is not
270
+ ymir config notice version off # silence one; `on` restores it
271
+ judgment[4]{key,what}:
272
+ "version","the line that says which version moved to which"
273
+ "patience","the long-hour words before a slow install or build"
274
+ "next","the where-to-go-from-here block at the end"
275
+ "hints","the one-line helpers that say how to hide a notice"
276
+ ```
277
+
278
+ Every notice that can be silenced **says so once**, in the cloth's faint voice —
279
+ `(not again: ymir config notice version off)` — so the way out is discoverable
280
+ without a manual. Absent means on; a preference is honoured by the shell cloth
281
+ (`notice_wanted`, `notice_hint`) and by the CLI alike. `ymir-install.sh`'s marks
282
+ and the raise path honour it too.
283
+
284
+ ### Four surfaces, one raise
285
+
286
+ `ymir raise` lifts the whole hall — the SPA (`:3888`), Óðrerir (`:4322`), the
287
+ board (`:8437`), and the commit-hall Sessrúmnir rises in the same motion (it is an
288
+ Electron app of its own, not a browser surface). `ymir-install.sh`'s desktop step
289
+ opens **one window per surface** rather than a single "both".
290
+
291
+ ### What a user went from, and to
292
+
293
+ `npm install -g` prints *"changed 266 packages"* and no versions. The CLI records
294
+ the version it last ran in the home's state (`state/version`) and says the
295
+ transition **once**, on the first run after an update:
296
+
297
+ ```
298
+ the tree moved 0.1.7 → 0.1.9
299
+ run `ymir eir` to see what stands, `ymir raise` to lift the hall
300
+ ```
301
+
302
+ ### The patience words — `style_patience`
303
+
304
+ A long hour must say what it is doing. `bin/ymir-style.sh` carries the line, and
305
+ the installer's long chain and the raise path's build open with it:
306
+
307
+ ```
308
+ ◆ much moves the halls are being stood up for the first time
309
+ this hour is long, and nothing of yours is lost in it —
310
+ roots come home, shapes are re-cut, names are set true again.
311
+ Your patience is noted, and it is earned.
312
+ ```
313
+
314
+ Written in the house voice, kept under four lines (density), and shown on stderr
315
+ with everything else the cloth renders.
316
+
195
317
  ### Two libs the packaged tree needs
196
318
 
197
319
  ```
@@ -62,6 +62,15 @@ This touches only the Brokk repo and its own worktrees, never anything under `pr
62
62
  Summarize what landed without internal vocabulary: which parts of the fleet are now on the latest, and which were left as-is and why.
63
63
  Surface any skipped target whose reason needs the Allfather's attention (a home with un-landed changes, local edits).
64
64
 
65
+ 5. **Re-check where the apps live — an update can move the shape.**
66
+ `bin/app-lib.sh` resolves a surface in a clone (`apps/<surface>`) or in a
67
+ package (`node_modules/@zerwiz/<package>`); `bin/smidja-lib.sh` and
68
+ `bin/electron-lib.sh` do the same for the smithy and a shell's runtime. After a
69
+ fast-forward, run `bin/ymir-plan.sh --phase 5` — if a surface that stood
70
+ installed now reads `BLOCKED`, the layout assumption moved and the resolver
71
+ needs the new shape. A long update should also say so: `style_patience` from
72
+ `bin/ymir-style.sh` opens the slow part in the house's own voice.
73
+
65
74
  5. **Read the plan after an update — the new version may expect more of this host.**
66
75
  A tracked change can add a step, a root, or a setting the running machine has not
67
76
  met yet. After a fast-forward that moved the instruction surface, ask what the
@@ -48,6 +48,35 @@ To move an existing home to a new place, re-run the install with the new value:
48
48
  YMIR_HOME=/path/to/new-home bin/ymir-install.sh
49
49
  ```
50
50
 
51
+ ## The doors — what the operator types afterwards
52
+
53
+ Two commands land on PATH (`ymir`, `ymir-install`); everything else is a door on
54
+ `ymir`, named for the figure who does the work. `ymir --help` lists them all.
55
+
56
+ ```
57
+ ymir raise | lower lift the hall, or lay it down
58
+ ymir hlidskjalf | sessrumnir the two windows
59
+ ymir smidja the board on :8437 — build · start · stop · status
60
+ ymir heimdall [invite] the way in, and letting someone else in
61
+ ymir eir what stands, and mend what does not
62
+ ymir groa [migrate] take the latest, and mend this home forward
63
+ ymir mimir · sense · plan
64
+ ```
65
+
66
+ ## Both shapes — a clone and an npm install
67
+
68
+ The operator may have cloned the tree or installed the package; the scripts must
69
+ not care. `bin/app-lib.sh` resolves a surface either way (`apps/<surface>`, else
70
+ `node_modules/@zerwiz/<package>`), `bin/smidja-lib.sh` resolves the smithy, and
71
+ `bin/electron-lib.sh` verifies a shell's runtime. To exercise the *other* shape
72
+ without a second machine:
73
+
74
+ ```bash
75
+ YMIR_ROOT_DIR=/path/to/an/npm/@zerwiz/ymir bash bin/app-lib.sh # resolve against it
76
+ ```
77
+
78
+ That variable is how a resolver is tested against a package from a clone.
79
+
51
80
  ## Which root holds what
52
81
 
53
82
  ```
package/CHANGELOG.md CHANGED
@@ -1,4 +1,123 @@
1
1
 
2
+ ## 2026-09-17 — 0.1.11: four surfaces, their marks, and a door that mends
3
+
4
+ - **All four surfaces rise from one npm install** — Hlidskjalf `:3888`, Óðrerir
5
+ `:4322`, the board `:8437`, and Sessrúmnir in its own window; the installer's
6
+ desktop step opens one window per surface.
7
+ - **All four launcher marks land** — the rune glyphs ship in the package
8
+ (`midgard/design-system/`), the mint resolves a packaged app, the apps'
9
+ templates are written before the rune marks so the themed name stands, and a
10
+ retired name is swept from the launcher.
11
+ - **`ymir hlidskjalf` mends the Electron runtime itself** before refusing, because
12
+ npm gates install scripts and a fresh user could otherwise have no window at all.
13
+ - **Every notice can be silenced once and remembered** — `ymir config notice
14
+ version|patience|next|hints off`.
15
+ - **Óðrerir serves its build** from a package, and says what failed when it cannot.
16
+
17
+ ## 2026-09-17 — the sweep, and the door that mends itself
18
+
19
+ - **A retired name is swept from the launcher.** `ymir-visualizer.desktop` and its
20
+ icon pointed at a glyph nobody ships — a blank square for anyone who knew the old
21
+ name. `design-icon.sh install` removes retired entries now (`ymir-visualizer`,
22
+ and `ymir-hlidskjalf-mobile` where that app is not installed).
23
+ - **`ymir hlidskjalf` mends the Electron runtime itself.** npm gates install
24
+ scripts by default, so a fresh install can build the web app perfectly and have
25
+ no window. The door now runs the repair it used to *describe*
26
+ (`npm install-scripts approve electron` + `npm rebuild electron`), and only then
27
+ refuses — with both the manual command and the honest alternative
28
+ (`ymir install --no-desktop`).
29
+ - **Two faults are provably in the app repos, not here** (recorded so nobody hunts
30
+ them in Ymir again):
31
+ - **the fourth hall** — the landing's list is a hardcoded SPA component
32
+ (`HallsSwitcher`; the bundle says *"three halls of Ymir — one seat, three
33
+ roofs"*), not data from the gate API. Óðrerir must be added in
34
+ `zerwiz/hlidskjalf` and the SPA rebuilt.
35
+ - **the ember glow** — the seat ships `emberBootTheme` in its built renderer but
36
+ no themed cloth beside it; the house's framework-neutral
37
+ `midgard/design-system/ember.js` exists precisely so every surface can share
38
+ one fire, and the seat must be told to light it (`zerwiz/sessrumnir`).
39
+
40
+ ## 2026-09-17 — "not again", and four surfaces in one raise
41
+
42
+ - **Every notice can be silenced.** A line is information once and noise the
43
+ tenth: the version that moved, the long-hour words, the next-steps block, the
44
+ hints. Each now says so **once** in the cloth's faint voice — *not again:
45
+ `ymir config notice version off`* — and the preference is recorded in the
46
+ operator's settings, honoured by the shell cloth and the CLI alike. `ymir config
47
+ show` is the door; four keys: `version · patience · next · hints`.
48
+ - **The package carries the design system.** It never shipped `midgard/`, so the
49
+ rune glyphs — the icons themselves — were absent and every mark failed with
50
+ *no glyph ansuz*. `midgard/design-system/` is in `files[]` now: 30 files, 22
51
+ glyphs.
52
+ - **The marks are written in the right order.** The apps' own launcher templates
53
+ landed *after* the rune marks and overwrote the themed name with an absolute
54
+ `.png`; the templates go first now and the rune marks stand over them.
55
+ - **Óðrerir rises from a package.** Its published tarball carries `dist` and a
56
+ `dev` command that calls a script it does not carry, so `npm run dev` could never
57
+ work from a package — the hall serves its **build** (`astro preview`) where there
58
+ is one, exactly as the SPA does. And its dependencies' absence is no longer
59
+ silent: the hall says what failed and why.
60
+ - **Four surfaces, one raise.** `ymir raise` lifts the SPA, Óðrerir, the board and
61
+ Sessrúmnir together; the installer's desktop step opens **one window per
62
+ surface**, not a single "both".
63
+ - **A library must not end a function on a failing test.** `style_init` did, and
64
+ `scripts/start.sh` runs under `set -e` — so the cloth took the whole raise down
65
+ with it, silently. `return 0`, and the reason is written beside it.
66
+
67
+ ## 2026-09-17 — 0.1.10: the hall answers on its port, and the marks land
68
+
69
+ - **The SPA listens where it is asked.** A bare `npm run dev` let Vite take 5173,
70
+ so `:3888` answered nothing in either shape; a packaged tree now serves its built
71
+ `./dist` and every shape passes `--port "$HLIDSKJALF_PORT" --strictPort`.
72
+ - **Óðrerir resolves** through `bin/app-lib.sh` instead of the clone's `apps/`.
73
+ - **The launcher entries and rune icons land on any Linux desktop** — the
74
+ freedesktop half of desktop integration is no longer behind Omarchy's gate, so a
75
+ GNOME operator gets marks at all.
76
+ - **The version a user went from and to** is said once, on the first run after an
77
+ update: *the tree moved 0.1.9 → 0.1.10*.
78
+
79
+ ## 2026-09-17 — npm-only: the hall answers on its own port, and the marks land on any desktop
80
+
81
+ The Allfather turned off every service running from the clone and asked for the
82
+ npm path alone to be true.
83
+
84
+ - **The SPA never listened on `:3888`.** `scripts/start.sh` raised it with a bare
85
+ `npm run dev`, so Vite took 5173 — then 5174 — while `ymir-validate.sh` and
86
+ every door looked for `:3888`. The port is now our decision
87
+ (`--port "$HLIDSKJALF_PORT" --strictPort`), a packaged install serves its built
88
+ `./dist` through `vite preview` instead of a dev server, and the raise prints
89
+ the last lines of the log when the port does not answer instead of claiming
90
+ success.
91
+ - **Óðrerir was skipped on a packaged install** — `HALL_DIR="$ROOT/apps/odrerir"`,
92
+ the clone's layout again. It resolves through `bin/app-lib.sh` now
93
+ (`app_dir odrerir`), like every other surface.
94
+ - **The launcher entries and the icons are the freedesktop half, not Omarchy's.**
95
+ Both halves of `bin/desktop-place.sh` sat behind the Omarchy gate, so a GNOME
96
+ operator got *nothing* — no entries, no rune icons — which is why the desktop
97
+ marks from the clone existed and the ones from npm never appeared. `entries` is
98
+ its own verb now (any Linux desktop), the installer's `marks` step calls it, and
99
+ the desktop database and icon cache are refreshed afterwards.
100
+ - **A user can see what moved.** `npm install -g` says *"changed 266 packages"* and
101
+ no versions. The CLI records the version it last ran and says the transition
102
+ once: *the tree moved 0.1.7 → 0.1.9 · run `ymir eir` to see what stands*.
103
+
104
+ ## 2026-09-17 — the doors, written down where a user will find them
105
+
106
+ - **`README.md` gains "After it installs — the doors"**: the two commands on PATH,
107
+ the twelve doors on `ymir`, the lawless name that answers once and points at the
108
+ lawful one, the patience words in full, and a section on **both shapes** — a
109
+ clone and a package, with one resolver and one rule (colour for the eye, TOON
110
+ for the pipe).
111
+ - **The skills that must know are taught.** Galdr's `assets/installation.md` gains
112
+ the shape table, the eighteen converted call sites, and the trap that named
113
+ itself (`printf -v` writes to the function's scope — scratch names are
114
+ function-prefixed for that reason). The `ymir-host` skill's `assets/install.md`
115
+ gains the doors and how to exercise the *other* shape without a second machine
116
+ (`YMIR_ROOT_DIR`). The `groa-update` skill gains the step that matters after
117
+ every update: **re-check where the apps live** — a shape assumption is a bug
118
+ that only shows itself on the other shape — and say the long hour in the house's
119
+ voice.
120
+
2
121
  ## 2026-09-17 — 0.1.9: both shapes reach the registry
3
122
 
4
123
  - **`ymir raise` works on a packaged install.** 0.1.8 shipped before the resolver,
package/README.md CHANGED
@@ -666,3 +666,57 @@ packages (Hlidskjalf, Óðrerir, Sessrúmnir, Smíðja) — one per app.
666
666
  Private data never touches npm — all user data lives at `$YMIR_HOME` (the hoard
667
667
  at `hodd/` and the realms at `svartalfaheim/`) and syncs via the user's
668
668
  **private** GitHub repo, never a public one.
669
+
670
+ ### After it installs — the doors
671
+
672
+ `npm install -g @zerwiz/ymir` puts **two** commands on your PATH: `ymir` and
673
+ `ymir-install`. Everything else is a door on `ymir`, named for the figure whose
674
+ work it does — because a great many doors either open or they do not, and a name
675
+ that says whose hand is on it is the difference between guessing and knowing.
676
+
677
+ ```
678
+ ymir first setup: the plan, then your word
679
+ ymir raise | lower lift the hall, or lay it down
680
+ ymir hlidskjalf the high seat's window
681
+ ymir smidja the smithy's board (:8437) — build · start · stop · status
682
+ ymir sessrumnir the seat-hall's window
683
+ ymir heimdall the way in — your credential (status · set · github)
684
+ ymir invite let someone else in — mint · list · revoke
685
+ ymir eir what stands, and mend what does not
686
+ ymir groa [migrate] take the latest, and mend this home forward
687
+ ymir mimir · sense · plan
688
+ ```
689
+
690
+ A name the law has not given a home still answers, once, and tells you the name
691
+ that has it: `ymir doctor` replies *the door is named `ymir eir` now*.
692
+
693
+ **A long hour says so, in its own voice.** The installer opens with the plan and,
694
+ when the work will take a while, tells you plainly:
695
+
696
+ ```
697
+ ◆ much moves the halls are being stood up for the first time
698
+ this hour is long, and nothing of yours is lost in it —
699
+ roots come home, shapes are re-cut, names are set true again.
700
+ Your patience is noted, and it is earned.
701
+ ```
702
+
703
+ ### Both shapes — a clone and a package are one tree
704
+
705
+ Ymir runs the same whether you **cloned** it or **installed** it from npm. The
706
+ apps are the difference, and only in where they live:
707
+
708
+ | | a clone | an npm install |
709
+ |---|---|---|
710
+ | the surfaces | `apps/hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory` | `node_modules/@zerwiz/hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory` |
711
+ | how they got there | the registry's `repo: apps/<path>` block clones each | declared as dependencies of the distro |
712
+
713
+ One resolver (`bin/app-lib.sh`) answers for both, so a script never has to care
714
+ which shape it woke in — and the same is true of the smithy (`bin/smidja-lib.sh`)
715
+ and of a desktop shell's runtime (`bin/electron-lib.sh`). The plan tells you which
716
+ surfaces are present, which are declared but not yet fetched, and which have no
717
+ package at all.
718
+
719
+ **Colour is for the eye, never for the pipe.** `bin/ymir-style.sh` renders in the
720
+ halls' own cloth — bone for words, bronze for what acts, steel for what stands,
721
+ blood for what is wrong — and only when a human is watching (a terminal, no
722
+ `NO_COLOR`). What a pipeline reads on stdout stays plain TOON.
@@ -75,8 +75,18 @@ mint() { # <app-dir> <glyph> <tint> <label>
75
75
  local path
76
76
  path="$(sed -n 's/.*<path d="\([^"]*\)".*/\1/p' "$g" | head -1)"
77
77
  [ -n "$path" ] || { printf 'error: %s carries no <path> to reuse\n' "$glyph" >&2; return 1; }
78
- local outdir="$ROOT/$dir/public"
79
- [ -d "$(dirname "$outdir")" ] || { printf ' "%s","SKIP (no such app)"\n' "$dir"; return 0; }
78
+ # A clone keeps the app at $ROOT/apps/<name>; a package keeps it under
79
+ # node_modules/@zerwiz/<package>. The mint resolves both, so a packaged install
80
+ # gets its favicon too — the same resolver every other script uses.
81
+ local base outdir surface
82
+ base="$ROOT/$dir"
83
+ if [ ! -d "$base" ]; then
84
+ surface="$(basename "$dir")"
85
+ case "$surface" in smidja-factory) surface=smidja ;; esac
86
+ app_dir "$surface" base 2>/dev/null || base=""
87
+ fi
88
+ [ -n "$base" ] && [ -d "$base" ] || { printf ' "%s","SKIP (no such app)"\n' "$dir"; return 0; }
89
+ outdir="$base/public"
80
90
  mkdir -p "$outdir"
81
91
  {
82
92
  printf '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="%s">\n' "$label"
@@ -147,7 +157,12 @@ install_all() {
147
157
  printf ' "%s","%s.svg","%s.desktop"\n' "$iconname" "$iconname" "$iconname"
148
158
  done
149
159
  [ -f "$apps_dir/ymir-smidja.desktop" ] && rm -f "$apps_dir/ymir-smidja.desktop"
150
- for stale in ymir-hlidskjalf-mobile; do rm -f "$apps_dir/$stale.desktop" "$icons_dir/$stale.svg"; done
160
+ # Names we have retired, swept on every install: a launcher entry that points at
161
+ # an icon nobody ships is a blank square, and the naming law does not let an old
162
+ # name linger beside the new one. (ymir-visualizer → ymir-smidja.)
163
+ for stale in ymir-hlidskjalf-mobile ymir-visualizer; do
164
+ rm -f "$apps_dir/$stale.desktop" "$icons_dir/$stale.svg"
165
+ done
151
166
  command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps_dir" >/dev/null 2>&1 || true
152
167
  }
153
168
 
@@ -51,12 +51,17 @@ CLASS_smidja="ymir-smidja"
51
51
  CLASS_sessrumnir="sessrumnir"
52
52
  CLASS_odrerir="ymir-odrerir"
53
53
 
54
- # ── launcher entries (Omarchy / Linux desktop only) ──────────────────────────
54
+ # ── launcher entries (ANY Linux desktop — GNOME, KDE, Hyprland) ──────────────
55
+ # Two halves live in this file, and they are not the same kind of thing:
56
+ # · the WINDOW RULES are Hyprland's (Omarchy) — numbered desktops, a Lua rule;
57
+ # · the LAUNCHER ENTRIES and the app icons are the freedesktop standard, which
58
+ # GNOME, KDE and Hyprland all read from the same place.
59
+ # A GNOME operator gets entries and icons and no window rules, which is the
60
+ # correct answer — and the reason a packaged install on GNOME had no icons at
61
+ # all, because both halves lived behind the Omarchy gate.
62
+ #
55
63
  # The .desktop files are templates (__YMIR_ROOT__, not an absolute path), because
56
- # where this checkout lives is a fact about the machine, not about Ymir. They are
57
- # rendered into the user's applications directory. This is DESKTOP INTEGRATION —
58
- # Omarchy/Hyprland is the supported environment; another OS needs its own launcher,
59
- # not a branch of this one.
64
+ # where this checkout lives is a fact about the machine, not about Ymir.
60
65
  install_entries() {
61
66
  local dst="$HOME/.local/share/applications" n=0
62
67
  local dirs=("$APP_HLIDSKJALF/electron" "$APP_SESSRUMNIR/resources" "$APP_ODRERIR/electron")
@@ -195,6 +200,16 @@ include_launchers_file() {
195
200
  }
196
201
 
197
202
  case "$ACTION" in
203
+ entries)
204
+ # The launcher half on its own: any Linux desktop, no Hyprland required.
205
+ install_entries
206
+ if command -v update-desktop-database >/dev/null 2>&1; then
207
+ update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true
208
+ fi
209
+ if command -v gtk-update-icon-cache >/dev/null 2>&1 && [ -d "$HOME/.local/share/icons/hicolor" ]; then
210
+ gtk-update-icon-cache -q -t -f "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true
211
+ fi
212
+ ;;
198
213
  plan)
199
214
  plan="$(plan_desktops)"
200
215
  printf 'desktop-place[%d]{app,desktop}:\n' "$(printf '%s\n' "$plan" | grep -c .)"
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # ymir-config.sh — the operator's own preferences, honoured and remembered.
3
+ #
4
+ # Some lines a script prints are information the first time and noise the tenth:
5
+ # the version that moved, the long-hour words, the hints. A user must be able to
6
+ # say "not again" — once — and be believed. That is what this door is for.
7
+ #
8
+ # ymir-config.sh show # every preference and its state
9
+ # ymir-config.sh notice <key> off|on # silence or restore one notice
10
+ # ymir-config.sh notice <key> # what it is now
11
+ #
12
+ # The store is the operator's settings, in the home they chose — never the code
13
+ # tree (Rule 04) — one `key=on|off` line per name:
14
+ #
15
+ # <home>/config/notices.conf
16
+ #
17
+ # keys: version the "the tree moved x → y" line
18
+ # patience the long-hour words before a slow install or build
19
+ # next the "where to go from here" block
20
+ # hints the one-line helpers ("hide this next time: …")
21
+ #
22
+ # Exit: 0 ok, 2 usage.
23
+ set -u
24
+
25
+ VERSION="1.0.0"
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ # shellcheck source=bin/hoard-lib.sh
28
+ . "$SCRIPT_DIR/hoard-lib.sh"
29
+ hoard_settings_dir SETTINGS
30
+ STORE="$SETTINGS/notices.conf"
31
+ KNOWN="version patience next hints"
32
+
33
+ usage() { sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; }
34
+
35
+ state_of() { # <key> → on|off (absent = on)
36
+ local key="$1" line
37
+ [ -r "$STORE" ] || { printf 'on'; return 0; }
38
+ line="$(grep -m1 "^${key}=" "$STORE" 2>/dev/null || true)"
39
+ [ -n "$line" ] && printf '%s' "${line#*=}" || printf 'on'
40
+ }
41
+
42
+ set_state() { # <key> <on|off>
43
+ local key="$1" value="$2" tmp
44
+ case "$value" in on|off) ;; *) printf 'error: %s is not on or off\nhelp: ymir config notice %s off\n' "$value" "$key" >&2; exit 2 ;; esac
45
+ mkdir -p "$SETTINGS" 2>/dev/null || { printf 'error: cannot write %s\n' "$SETTINGS" >&2; exit 1; }
46
+ tmp="$(mktemp)"; chmod 600 "$tmp"
47
+ if [ -r "$STORE" ]; then grep -v "^${key}=" "$STORE" >"$tmp" 2>/dev/null || true; fi
48
+ printf '%s=%s\n' "$key" "$value" >>"$tmp"
49
+ mv "$tmp" "$STORE"
50
+ }
51
+
52
+ case "${1-}" in
53
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
54
+ -h|--help|"") usage; exit 0 ;;
55
+ show)
56
+ printf 'notices[%d]{key,state,what}:\n' 4
57
+ printf ' "version","%s","the line that says which version moved to which"\n' "$(state_of version)"
58
+ printf ' "patience","%s","the long-hour words before a slow install or build"\n' "$(state_of patience)"
59
+ printf ' "next","%s","the where-to-go-from-here block at the end"\n' "$(state_of next)"
60
+ printf ' "hints","%s","the one-line helpers that say how to hide a notice"\n' "$(state_of hints)"
61
+ printf 'store[1]{path}:\n "%s"\n' "$STORE"
62
+ ;;
63
+ notice)
64
+ key="${2-}"; value="${3-}"
65
+ [ -n "$key" ] || { printf 'error: which notice?\nhelp: ymir config notice <version|patience|next|hints> off\n' >&2; exit 2; }
66
+ case " $KNOWN " in *" $key "*) ;; *) printf 'error: unknown notice %s\nhelp: one of: %s\n' "$key" "$KNOWN" >&2; exit 2 ;; esac
67
+ if [ -z "$value" ]; then
68
+ printf 'notices[1]{key,state}:\n "%s","%s"\n' "$key" "$(state_of "$key")"
69
+ else
70
+ set_state "$key" "$value"
71
+ printf 'notices[1]{key,state}:\n "%s","%s"\n' "$key" "$value"
72
+ [ "$value" = off ] && printf 'this notice will not be shown again — restore it with: ymir config notice %s on\n' "$key"
73
+ fi
74
+ ;;
75
+ *) printf 'error: unknown action %s\nhelp: bin/ymir-config.sh [show|notice <key> on|off]\n' "$1" >&2; exit 2 ;;
76
+ esac
@@ -670,6 +670,13 @@ step_marks() {
670
670
  add marks OK "$n desktop app marks installed"
671
671
  return
672
672
  fi
673
+ # The apps' own launcher templates land first (the freedesktop half of desktop
674
+ # integration, for ANY Linux desktop), and the rune marks are minted and
675
+ # installed over them — so the themed rune name wins for the surfaces that have
676
+ # one, instead of a template's absolute .png.
677
+ if [ -x "$SCRIPT_DIR/desktop-place.sh" ]; then
678
+ "$SCRIPT_DIR/desktop-place.sh" entries >/dev/null 2>&1 || true
679
+ fi
673
680
  "$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
674
681
  n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
675
682
  [ -r "$HOME/.pi/agent/AGENTS.md" ] || [ -d "$HOME/.pi/agent" ] && {
@@ -720,10 +727,16 @@ step_desktop() {
720
727
  add desktop WARN "the Electron runtime is PARTIAL for:$partial — the web surfaces stand; approve and rebuild to launch the shells"
721
728
  return 0
722
729
  fi
723
- if "$ROOT/scripts/electron.sh" start --both >/dev/null 2>&1; then
724
- add desktop OK "raised Hlidskjalf + Smíðja"
730
+ # One window per surface that is here: the halls are not one app.
731
+ raised=0
732
+ for _v in hlidskjalf smidja odrerir; do
733
+ "$ROOT/scripts/electron.sh" start --view "$_v" >/dev/null 2>&1 && raised=$((raised+1))
734
+ done
735
+ [ -x "$ROOT/bin/sessrumnir.sh" ] && "$ROOT/bin/sessrumnir.sh" start >/dev/null 2>&1 && raised=$((raised+1))
736
+ if [ "$raised" -ge 2 ]; then
737
+ add desktop OK "raised $raised window(s) — Hlidskjalf · Smíðja · Óðrerir · Sessrúmnir"
725
738
  else
726
- add desktop WARN "could not raise the desktop apps — run scripts/electron.sh start --both"
739
+ add desktop WARN "could not raise the desktop apps — ymir hlidskjalf | smidja | sessrumnir"
727
740
  fi
728
741
  }
729
742
 
package/bin/ymir-style.sh CHANGED
@@ -38,6 +38,10 @@ style_init() {
38
38
  fi
39
39
  C_OFF=""
40
40
  [ "$STYLE_ON" = 1 ] && C_OFF=$'\033[0m'
41
+ # A sourced library must never end a function on a FAILING test: callers run
42
+ # under `set -e` (scripts/start.sh does), and the last command's status becomes
43
+ # the function's — so a caller raised the whole hall and died at the cloth.
44
+ return 0
41
45
  }
42
46
 
43
47
  # The forge's marks, one per state a thing can be in.
@@ -64,6 +68,23 @@ style_colour() { # <state> → the colour for it
64
68
  esac
65
69
  }
66
70
 
71
+ # --- preferences: a user must be able to say "not again", once --------------
72
+ # The store is the operator's settings (see bin/ymir-config.sh); absent means on.
73
+ notice_state() { # <key> → on|off
74
+ local key="$1" store line
75
+ if [ -n "${YMIR_SETTINGS_DIR:-}" ]; then store="$YMIR_SETTINGS_DIR/notices.conf"
76
+ else store="${YMIR_HOME:-$HOME/Documents/Ymir}/config/notices.conf"; fi
77
+ [ -r "$store" ] || { printf 'on'; return 0; }
78
+ line="$(grep -m1 "^${key}=" "$store" 2>/dev/null || true)"
79
+ [ -n "$line" ] && printf '%s' "${line#*=}" || printf 'on'
80
+ }
81
+ notice_wanted() { [ "$(notice_state "${1-}")" != off ]; }
82
+ notice_hint() { # <key> — said once, and never when the hints are silenced
83
+ [ "$(notice_state hints)" = off ] && return 0
84
+ printf '%s
85
+ ' "${C_FAINT} (not again: ymir config notice ${1-} off)${C_OFF}" >&2
86
+ }
87
+
67
88
  # --- the pieces -------------------------------------------------------------
68
89
 
69
90
  # The mark: four lines at most. The WORDS show even in a pipe — only the colour
@@ -99,18 +120,21 @@ style_heading() { printf '\n%s%s%s\n' "$C_BRONZE" "$1" "$C_OFF" >&2; }
99
120
  # knows the halls are being set right waits; a user watching a silent cursor
100
121
  # wonders whether it has broken.
101
122
  style_patience() { # [what is being set right]
123
+ notice_wanted patience || return 0
102
124
  local what="${1:-the halls are being set right}"
103
125
  printf '\n' >&2
104
126
  style_line DO "much moves" "$what"
105
127
  style_hint " this hour is long, and nothing of yours is lost in it —"
106
128
  style_hint " roots come home, shapes are re-cut, names are set true again."
107
129
  style_hint " Your patience is noted, and it is earned."
130
+ notice_hint patience
108
131
  printf '\n' >&2
109
132
  }
110
133
 
111
134
  # The ending: what stands, then exactly what to type next. Every CLI deserves
112
135
  # to leave the operator with the next step and nothing else to guess.
113
136
  style_next() { # one command per line, as "verb — what it does"
137
+ notice_wanted next || return 0
114
138
  printf '\n%s\n' "${C_BOLD}Where to go from here${C_OFF}" >&2
115
139
  while [ $# -gt 0 ]; do
116
140
  local cmd="${1%% — *}" what="${1#* — }"