@zerwiz/ymir 0.1.10 → 0.1.12

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 (41) hide show
  1. package/.agents/skills/galdr-ymirsystem/assets/installation.md +29 -0
  2. package/CHANGELOG.md +100 -0
  3. package/bin/app-lib.sh +11 -1
  4. package/bin/design-icon.sh +32 -14
  5. package/bin/ymir-config.sh +76 -0
  6. package/bin/ymir-install.sh +15 -9
  7. package/bin/ymir-style.sh +24 -0
  8. package/bin/ymir.js +16 -1
  9. package/midgard/README.md +16 -0
  10. package/midgard/design-system/.gitkeep +0 -0
  11. package/midgard/design-system/ember.d.ts +13 -0
  12. package/midgard/design-system/ember.js +183 -0
  13. package/midgard/design-system/icons/algiz.svg +4 -0
  14. package/midgard/design-system/icons/ansuz.svg +4 -0
  15. package/midgard/design-system/icons/berkana.svg +4 -0
  16. package/midgard/design-system/icons/dagaz.svg +4 -0
  17. package/midgard/design-system/icons/ehwaz.svg +4 -0
  18. package/midgard/design-system/icons/fehu.svg +4 -0
  19. package/midgard/design-system/icons/gjallarhorn.svg +4 -0
  20. package/midgard/design-system/icons/gungnir.svg +4 -0
  21. package/midgard/design-system/icons/heimdall.svg +4 -0
  22. package/midgard/design-system/icons/ingwaz.svg +4 -0
  23. package/midgard/design-system/icons/jera.svg +4 -0
  24. package/midgard/design-system/icons/kaunan.svg +4 -0
  25. package/midgard/design-system/icons/midgard.svg +4 -0
  26. package/midgard/design-system/icons/mimirsbrunn.svg +4 -0
  27. package/midgard/design-system/icons/othala.svg +4 -0
  28. package/midgard/design-system/icons/raidho.svg +4 -0
  29. package/midgard/design-system/icons/ratatoskr.svg +4 -0
  30. package/midgard/design-system/icons/sowilo.svg +4 -0
  31. package/midgard/design-system/icons/tiwaz.svg +4 -0
  32. package/midgard/design-system/icons/utgard.svg +4 -0
  33. package/midgard/design-system/icons/valhalla.svg +4 -0
  34. package/midgard/design-system/icons/yggdrasil.svg +4 -0
  35. package/midgard/design-system/icons.md +59 -0
  36. package/midgard/design-system/runes.md +78 -0
  37. package/midgard/design-system/tokens.css +109 -0
  38. package/midgard/design-system/ymir-mark.svg +62 -0
  39. package/package.json +3 -2
  40. package/scripts/electron.sh +18 -2
  41. package/scripts/start.sh +53 -7
@@ -259,6 +259,35 @@ clone's marks already existed. `bin/desktop-place.sh entries` is the launcher ha
259
259
  alone (any Linux desktop), the installer's `marks` step calls it, and both the
260
260
  desktop database and the icon cache are refreshed after.
261
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
+
262
291
  ### What a user went from, and to
263
292
 
264
293
  `npm install -g` prints *"changed 266 packages"* and no versions. The CLI records
package/CHANGELOG.md CHANGED
@@ -1,4 +1,104 @@
1
1
 
2
+ ## 2026-09-17 — 0.1.12: the icons reach the live tree, and a missing surface is a failure
3
+
4
+ - **The launcher entries name the door** (`Exec=ymir hlidskjalf`, …) instead of an absolute path into whichever tree minted them — the fault that made every icon do nothing.
5
+ - **All three npm shapes resolve**: a clone (`apps/<app>`), a nested package, and a **sibling** package (`<prefix>/lib/node_modules/@zerwiz/<app>`), which is where a global install puts a dependency it hoists out of the distro.
6
+ - **@zerwiz/sessrumnir pinned exact** — a prerelease range resolved in one prefix and was skipped in another.
7
+ - **A missing surface is a FAILURE with the remedy**, never "skipped".
8
+
9
+ ## 2026-09-17 — the icons reach the live tree, and a missing surface is a failure
10
+
11
+ The Allfather clicked the launcher icons and nothing happened. Both faults were
12
+ mine, and both are the same mistake wearing different clothes: **something named a
13
+ path where it should have named a door.**
14
+
15
+ - **The entry launched a temp directory.** Every `.desktop` file carried
16
+ `Exec=bash <the tree that minted it>/scripts/electron.sh …` — and I had minted
17
+ from `/tmp/opencode/…` while testing, so every icon on his launcher pointed into
18
+ a scratch tree. Entries now name the **door**: `Exec=ymir hlidskjalf`, resolved
19
+ from PATH, so a click reaches whichever install is live. A launcher entry must
20
+ outlive the tree that wrote it.
21
+ - **The fourth surface was missing and the raise shrugged.** `Sessrúmnir — skipped
22
+ (the sessrumnir surface is not present)` and the raise carried on. It was a
23
+ **sibling**: npm put it at `<prefix>/lib/node_modules/@zerwiz/sessrumnir`, beside
24
+ ymir, not inside it. The resolver knew a clone (`apps/<app>`) and a nested
25
+ package (`<pkg>/node_modules/@zerwiz/<app>`) but not that third shape — which is
26
+ the one a **global** install uses when it hoists a dependency out of the distro.
27
+ All three are resolved now, and a surface that is absent is a **FAILURE with the
28
+ exact remedy**, never a shrug.
29
+ - **A dependency must resolve every time.** `@zerwiz/sessrumnir` was declared as a
30
+ prerelease range (`^0.1.8-alpha`): npm resolved it in one prefix and skipped it
31
+ in another. Pinned to the exact version.
32
+
33
+ Verified from his own install (`~/.npm-global`): Hlidskjalf `:3888`, Óðrerir
34
+ `:4322`, the board `:8437`, the gate API `:3889` (401 — up, auth enforced), Nornir
35
+ cron started, Bifrost and the well up, and the launcher entries naming the doors.
36
+
37
+ ## 2026-09-17 — 0.1.11: four surfaces, their marks, and a door that mends
38
+
39
+ - **All four surfaces rise from one npm install** — Hlidskjalf `:3888`, Óðrerir
40
+ `:4322`, the board `:8437`, and Sessrúmnir in its own window; the installer's
41
+ desktop step opens one window per surface.
42
+ - **All four launcher marks land** — the rune glyphs ship in the package
43
+ (`midgard/design-system/`), the mint resolves a packaged app, the apps'
44
+ templates are written before the rune marks so the themed name stands, and a
45
+ retired name is swept from the launcher.
46
+ - **`ymir hlidskjalf` mends the Electron runtime itself** before refusing, because
47
+ npm gates install scripts and a fresh user could otherwise have no window at all.
48
+ - **Every notice can be silenced once and remembered** — `ymir config notice
49
+ version|patience|next|hints off`.
50
+ - **Óðrerir serves its build** from a package, and says what failed when it cannot.
51
+
52
+ ## 2026-09-17 — the sweep, and the door that mends itself
53
+
54
+ - **A retired name is swept from the launcher.** `ymir-visualizer.desktop` and its
55
+ icon pointed at a glyph nobody ships — a blank square for anyone who knew the old
56
+ name. `design-icon.sh install` removes retired entries now (`ymir-visualizer`,
57
+ and `ymir-hlidskjalf-mobile` where that app is not installed).
58
+ - **`ymir hlidskjalf` mends the Electron runtime itself.** npm gates install
59
+ scripts by default, so a fresh install can build the web app perfectly and have
60
+ no window. The door now runs the repair it used to *describe*
61
+ (`npm install-scripts approve electron` + `npm rebuild electron`), and only then
62
+ refuses — with both the manual command and the honest alternative
63
+ (`ymir install --no-desktop`).
64
+ - **Two faults are provably in the app repos, not here** (recorded so nobody hunts
65
+ them in Ymir again):
66
+ - **the fourth hall** — the landing's list is a hardcoded SPA component
67
+ (`HallsSwitcher`; the bundle says *"three halls of Ymir — one seat, three
68
+ roofs"*), not data from the gate API. Óðrerir must be added in
69
+ `zerwiz/hlidskjalf` and the SPA rebuilt.
70
+ - **the ember glow** — the seat ships `emberBootTheme` in its built renderer but
71
+ no themed cloth beside it; the house's framework-neutral
72
+ `midgard/design-system/ember.js` exists precisely so every surface can share
73
+ one fire, and the seat must be told to light it (`zerwiz/sessrumnir`).
74
+
75
+ ## 2026-09-17 — "not again", and four surfaces in one raise
76
+
77
+ - **Every notice can be silenced.** A line is information once and noise the
78
+ tenth: the version that moved, the long-hour words, the next-steps block, the
79
+ hints. Each now says so **once** in the cloth's faint voice — *not again:
80
+ `ymir config notice version off`* — and the preference is recorded in the
81
+ operator's settings, honoured by the shell cloth and the CLI alike. `ymir config
82
+ show` is the door; four keys: `version · patience · next · hints`.
83
+ - **The package carries the design system.** It never shipped `midgard/`, so the
84
+ rune glyphs — the icons themselves — were absent and every mark failed with
85
+ *no glyph ansuz*. `midgard/design-system/` is in `files[]` now: 30 files, 22
86
+ glyphs.
87
+ - **The marks are written in the right order.** The apps' own launcher templates
88
+ landed *after* the rune marks and overwrote the themed name with an absolute
89
+ `.png`; the templates go first now and the rune marks stand over them.
90
+ - **Óðrerir rises from a package.** Its published tarball carries `dist` and a
91
+ `dev` command that calls a script it does not carry, so `npm run dev` could never
92
+ work from a package — the hall serves its **build** (`astro preview`) where there
93
+ is one, exactly as the SPA does. And its dependencies' absence is no longer
94
+ silent: the hall says what failed and why.
95
+ - **Four surfaces, one raise.** `ymir raise` lifts the SPA, Óðrerir, the board and
96
+ Sessrúmnir together; the installer's desktop step opens **one window per
97
+ surface**, not a single "both".
98
+ - **A library must not end a function on a failing test.** `style_init` did, and
99
+ `scripts/start.sh` runs under `set -e` — so the cloth took the whole raise down
100
+ with it, silently. `return 0`, and the reason is written beside it.
101
+
2
102
  ## 2026-09-17 — 0.1.10: the hall answers on its port, and the marks land
3
103
 
4
104
  - **The SPA listens where it is asked.** A bare `npm run dev` let Vite take 5173,
package/bin/app-lib.sh CHANGED
@@ -38,7 +38,17 @@ app_dir() { # <surface> <result-var> — a clone's apps/<x>, else the package
38
38
  _apd_root="${YMIR_ROOT_DIR:-}"
39
39
  if [ -z "$_apd_root" ]; then _apd_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; fi
40
40
  _apd_pkg="$(app_pkg "$_apd_surface")"
41
- for _apd_c in "$_apd_root/apps/$_apd_surface" "$_apd_root/node_modules/@zerwiz/$_apd_pkg"; do
41
+ # Three shapes, and npm uses the third more than anyone expects:
42
+ # a clone apps/<surface>
43
+ # a nested package <pkg>/node_modules/@zerwiz/<package>
44
+ # a SIBLING package <prefix>/lib/node_modules/@zerwiz/<package> — beside ymir,
45
+ # where a global install puts a dependency it hoists out
46
+ # (the sibling is found through dirname, so no parent-climbing path is written)
47
+ for _apd_c in \
48
+ "$_apd_root/apps/$_apd_surface" \
49
+ "$_apd_root/node_modules/@zerwiz/$_apd_pkg" \
50
+ "$(dirname "$_apd_root")/$_apd_pkg"
51
+ do
42
52
  [ -d "$_apd_c" ] && { printf -v "$_apd_rv" '%s' "$_apd_c"; return 0; }
43
53
  done
44
54
  printf -v "$_apd_rv" '%s' ""
@@ -43,11 +43,11 @@ ACTION="${1:-list}"; shift || true
43
43
  # dir|glyph|tint|says|icon-name|exec|wm-class
44
44
  # FIVE UI surfaces, FIVE DIFFERENT glyphs — no app borrows another's rune.
45
45
  APPS=(
46
- "apps/hlidskjalf|ehwaz|#c9973f|the seat — Hlidskjalf, the high seat of the control plane|ymir-hlidskjalf|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf|Ymir · Hlidskjalf"
47
- "apps/hlidskjalf-mobile|raidho|#c9973f|the road — the seat carried, Hlidskjalf on a phone|ymir-hlidskjalf-mobile|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf-mobile|Ymir · Hlidskjalf Mobile"
48
- "apps/odrerir|ansuz|#c9973f|the mead — Óðrerir, the Live Hall (Odin's breath, inspiration)|ymir-odrerir|scripts/electron.sh start --view odrerir|ymir-odrerir|Ymir · Óðrerir"
49
- "apps/sessrumnir|othala|#c9973f|the hall — Sessrúmnir, the seat that holds the cloth|ymir-sessrumnir|bin/sessrumnir.sh start|ymir-sessrumnir|Ymir · Sessrúmnir"
50
- "apps/smidja-factory/apps/visualizer|kaunan|#f59e0b|the torch — the forge's eye, Smíðja's trace|ymir-smidja|scripts/electron.sh start --view smidja|ymir-smidja|Ymir · Smíðja"
46
+ "apps/hlidskjalf|ehwaz|#c9973f|the seat — Hlidskjalf, the high seat of the control plane|ymir-hlidskjalf|ymir hlidskjalf|ymir-hlidskjalf|Ymir · Hlidskjalf"
47
+ "apps/hlidskjalf-mobile|raidho|#c9973f|the road — the seat carried, Hlidskjalf on a phone|ymir-hlidskjalf-mobile|ymir hlidskjalf|ymir-hlidskjalf-mobile|Ymir · Hlidskjalf Mobile"
48
+ "apps/odrerir|ansuz|#c9973f|the mead — Óðrerir, the Live Hall (Odin's breath, inspiration)|ymir-odrerir|ymir odrerir|ymir-odrerir|Ymir · Óðrerir"
49
+ "apps/sessrumnir|othala|#c9973f|the hall — Sessrúmnir, the seat that holds the cloth|ymir-sessrumnir|ymir sessrumnir|ymir-sessrumnir|Ymir · Sessrúmnir"
50
+ "apps/smidja-factory/apps/visualizer|kaunan|#f59e0b|the torch — the forge's eye, Smíðja's trace|ymir-smidja|ymir smidja|ymir-smidja|Ymir · Smíðja"
51
51
  )
52
52
 
53
53
  list() {
@@ -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"
@@ -105,11 +115,11 @@ icons_dir="$data_home/icons/hicolor/scalable/apps"
105
115
  apps_dir="$data_home/applications"
106
116
  # app | entry name | Exec | StartupWMClass
107
117
  ENTRIES=(
108
- "hlidskjalf|Ymir · Hlidskjalf|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf"
109
- "odrerir|Ymir · Óðrerir|scripts/electron.sh start --view odrerir|ymir-odrerir"
110
- "sessrumnir|Ymir · Sessrúmnir|bin/sessrumnir.sh start|ymir-sessrumnir"
111
- "visualizer|Ymir · Smíðja|scripts/electron.sh start --view smidja|ymir-smidja"
112
- "hlidskjalf-mobile|Ymir · Hlidskjalf Mobile|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf-mobile"
118
+ "hlidskjalf|Ymir · Hlidskjalf|ymir hlidskjalf|ymir-hlidskjalf"
119
+ "odrerir|Ymir · Óðrerir|ymir odrerir|ymir-odrerir"
120
+ "sessrumnir|Ymir · Sessrúmnir|ymir sessrumnir|ymir-sessrumnir"
121
+ "visualizer|Ymir · Smíðja|ymir smidja|ymir-smidja"
122
+ "hlidskjalf-mobile|Ymir · Hlidskjalf Mobile|ymir hlidskjalf|ymir-hlidskjalf-mobile"
113
123
  )
114
124
  install_all() {
115
125
  # A phone app gets an icon for its own bundle, never a desktop entry.
@@ -139,7 +149,10 @@ install_all() {
139
149
  printf '[Desktop Entry]\nType=Application\nVersion=1.0\n'
140
150
  printf 'Name=%s\n' "$display"
141
151
  printf 'Comment=%s\n' "$says"
142
- printf 'Exec=bash %s/%s\n' "$ROOT" "$exec"
152
+ # The door, never a path: a launcher entry outlives the tree that wrote it,
153
+ # and `ymir <door>` reaches whichever install is live. (The CLI is on PATH
154
+ # from npm's global bin, and from bin/ in a clone — ymir.js is a bin entry.)
155
+ printf 'Exec=%s\n' "$exec"
143
156
  printf 'Icon=%s\n' "$iconname"
144
157
  printf 'Terminal=false\nCategories=Development;Utility;\n'
145
158
  printf 'StartupWMClass=%s\nStartupNotify=true\n' "$klass"
@@ -147,7 +160,12 @@ install_all() {
147
160
  printf ' "%s","%s.svg","%s.desktop"\n' "$iconname" "$iconname" "$iconname"
148
161
  done
149
162
  [ -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
163
+ # Names we have retired, swept on every install: a launcher entry that points at
164
+ # an icon nobody ships is a blank square, and the naming law does not let an old
165
+ # name linger beside the new one. (ymir-visualizer → ymir-smidja.)
166
+ for stale in ymir-hlidskjalf-mobile ymir-visualizer; do
167
+ rm -f "$apps_dir/$stale.desktop" "$icons_dir/$stale.svg"
168
+ done
151
169
  command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps_dir" >/dev/null 2>&1 || true
152
170
  }
153
171
 
@@ -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,15 +670,15 @@ step_marks() {
670
670
  add marks OK "$n desktop app marks installed"
671
671
  return
672
672
  fi
673
- "$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
674
- n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
675
- # The launcher entries are the freedesktop half of desktop integration, and they
676
- # belong to ANY Linux desktop — not only to Omarchy's window rules. A GNOME
677
- # operator gets entries and icons and no window rules; before this, both halves
678
- # sat behind the Omarchy gate and a packaged install placed nothing at all.
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.
679
677
  if [ -x "$SCRIPT_DIR/desktop-place.sh" ]; then
680
678
  "$SCRIPT_DIR/desktop-place.sh" entries >/dev/null 2>&1 || true
681
679
  fi
680
+ "$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
681
+ n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
682
682
  [ -r "$HOME/.pi/agent/AGENTS.md" ] || [ -d "$HOME/.pi/agent" ] && {
683
683
  ln -sfn "$ROOT/AGENTS.md" "$HOME/.pi/agent/AGENTS.md" 2>/dev/null || true
684
684
  }
@@ -727,10 +727,16 @@ step_desktop() {
727
727
  add desktop WARN "the Electron runtime is PARTIAL for:$partial — the web surfaces stand; approve and rebuild to launch the shells"
728
728
  return 0
729
729
  fi
730
- if "$ROOT/scripts/electron.sh" start --both >/dev/null 2>&1; then
731
- 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"
732
738
  else
733
- 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"
734
740
  fi
735
741
  }
736
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#* — }"
package/bin/ymir.js CHANGED
@@ -54,8 +54,20 @@ const bold = wrap('1');
54
54
  // cannot see what moved. We can: record the version we last ran, and say the
55
55
  // transition once, on the first run after an update.
56
56
  const STATE_DIR = process.env.YMIR_STATE_DIR || path.join(process.env.YMIR_HOME || path.join(require('node:os').homedir(), 'Documents', 'Ymir'), 'state');
57
+ function noticeState(key) {
58
+ try {
59
+ const s = process.env.YMIR_SETTINGS_DIR
60
+ || path.join(process.env.YMIR_HOME || path.join(require('node:os').homedir(), 'Documents', 'Ymir'), 'config');
61
+ const f = path.join(s, 'notices.conf');
62
+ if (!fs.existsSync(f)) return 'on';
63
+ const line = fs.readFileSync(f, 'utf8').split('\n').find((l) => l.startsWith(key + '='));
64
+ return line ? line.slice(key.length + 1).trim() : 'on';
65
+ } catch { return 'on'; }
66
+ }
67
+
57
68
  function versionNotice() {
58
69
  try {
70
+ if (noticeState('version') === 'off') return;
59
71
  const f = path.join(STATE_DIR, 'version');
60
72
  const seen = fs.existsSync(f) ? fs.readFileSync(f, 'utf8').trim() : '';
61
73
  if (seen === pkg.version) return;
@@ -64,7 +76,9 @@ function versionNotice() {
64
76
  if (!seen) return; // a first install has nothing to compare
65
77
  process.stderr.write(
66
78
  bone(` the tree moved `) + faint(`${seen} → `) + bronze(`${pkg.version}`) + '\n' +
67
- faint(` run \`ymir eir\` to see what stands, \`ymir raise\` to lift the hall\n\n`));
79
+ faint(` run \`ymir eir\` to see what stands, \`ymir raise\` to lift the hall\n`) +
80
+ (noticeState('hints') === 'off' ? '' : faint(` (not again: ymir config notice version off)\n`)) +
81
+ '\n');
68
82
  } catch { /* a version notice must never break a door */ }
69
83
  }
70
84
 
@@ -83,6 +97,7 @@ const DOORS = {
83
97
  hlidskjalf: { script: 'scripts/electron.sh', about: "the high seat's window", args: ['start', '--view', 'hlidskjalf'] },
84
98
  sessrumnir: { script: 'scripts/electron.sh', about: "the seat-hall's window", args: ['start', '--view', 'sessrumnir'] },
85
99
  mimir: { script: 'bin/mimir.sh', about: 'the memory well' },
100
+ config: { script: 'bin/ymir-config.sh', about: 'your preferences — which notices are shown (stay silent with `notice <key> off`)' },
86
101
  sense: { script: 'bin/host-sense.sh', about: 'what THIS machine is' },
87
102
  plan: { script: 'bin/ymir-plan.sh', about: 'what an install would do here — writes nothing' },
88
103
  migrate: { script: 'bin/ymir-migrate.sh', about: "heal this home's structure forward (Gr\u00f3a's mend)" },
@@ -0,0 +1,16 @@
1
+ # MIDGARD — Shared Design Assets
2
+
3
+ Cross-tenant assets that are **public by design**: the design system and the
4
+ shared libraries any realm may pull.
5
+
6
+ **What does NOT belong here.** Company knowledge — policies, vision, specs,
7
+ client names, wiki pages. That is tenant data, scoped per company, and lives at
8
+ `$YMIR_HOME/hodd/identity/companies/`. This repo is public; a company wiki in it
9
+ would publish that company's internals.
10
+
11
+ ## Contents
12
+ - `design-system/` — shared UI components, design tokens, icon set
13
+ - `shared-packages/` — internal npm/cargo/python libraries (public)
14
+ - `infrastructure/` — shared Terraform / Docker / Kubernetes configs
15
+ - `github_org_repos/` — central repositories
16
+ - `shared-core-api/` — example shared repo (with `.yggdrasil/` worktrees)
File without changes
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Type declarations for the shared hearth (ember.js).
3
+ *
4
+ * The implementation is plain JS so any surface — React, Vue, Astro, or a bare
5
+ * page — can import the same fire. These types keep TypeScript consumers honest
6
+ * without forcing the module itself into a build step.
7
+ */
8
+
9
+ /** Start the hearth on <host> (a canvas, or an element to receive one). Returns a stop function. */
10
+ export function startEmbers(host: Element | null): () => void;
11
+
12
+ /** Light every element marked `data-ember`. Returns one stop function per hearth. */
13
+ export function startAllEmbers(root?: ParentNode): Array<() => void>;