@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
package/bin/ymir.js CHANGED
@@ -49,6 +49,39 @@ const faint = wrap('38;2;107;98;80');
49
49
  const blood = wrap('38;2;194;88;74');
50
50
  const bold = wrap('1');
51
51
 
52
+ // ── what a user went from, and to ────────────────────────────────────────────
53
+ // `npm install -g` prints "changed 266 packages" and no versions, so an operator
54
+ // cannot see what moved. We can: record the version we last ran, and say the
55
+ // transition once, on the first run after an update.
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
+
68
+ function versionNotice() {
69
+ try {
70
+ if (noticeState('version') === 'off') return;
71
+ const f = path.join(STATE_DIR, 'version');
72
+ const seen = fs.existsSync(f) ? fs.readFileSync(f, 'utf8').trim() : '';
73
+ if (seen === pkg.version) return;
74
+ fs.mkdirSync(STATE_DIR, { recursive: true });
75
+ fs.writeFileSync(f, pkg.version + '\n');
76
+ if (!seen) return; // a first install has nothing to compare
77
+ process.stderr.write(
78
+ bone(` the tree moved `) + faint(`${seen} → `) + bronze(`${pkg.version}`) + '\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');
82
+ } catch { /* a version notice must never break a door */ }
83
+ }
84
+
52
85
  // ── the doors ────────────────────────────────────────────────────────────────
53
86
  // verb → { script, args } — args are prepended to whatever the operator passes,
54
87
  // so a verb can be a doorway to a sub-verb of a script that has several.
@@ -64,6 +97,7 @@ const DOORS = {
64
97
  hlidskjalf: { script: 'scripts/electron.sh', about: "the high seat's window", args: ['start', '--view', 'hlidskjalf'] },
65
98
  sessrumnir: { script: 'scripts/electron.sh', about: "the seat-hall's window", args: ['start', '--view', 'sessrumnir'] },
66
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`)' },
67
101
  sense: { script: 'bin/host-sense.sh', about: 'what THIS machine is' },
68
102
  plan: { script: 'bin/ymir-plan.sh', about: 'what an install would do here — writes nothing' },
69
103
  migrate: { script: 'bin/ymir-migrate.sh', about: "heal this home's structure forward (Gr\u00f3a's mend)" },
@@ -108,6 +142,7 @@ function run(script, args) {
108
142
  const argv = process.argv.slice(2);
109
143
  const first = argv[0];
110
144
 
145
+ versionNotice();
111
146
  if (!first) run(DOORS.install.script, []);
112
147
  if (first === '--version' || first === '-v' || first === '-V') {
113
148
  process.stdout.write(`${pkg.version}\n`);
@@ -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>;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * ember.js — the hearth, in one place.
3
+ *
4
+ * The fire that warms Sessrúmnir's chat came from the landing page's hero
5
+ * (Ginnungagap): embers rising slowly with a gentle sway, over a few drifting
6
+ * haze pools. Sessrúmnir carries a React port; this is the framework-neutral
7
+ * original, so every surface can share one fire instead of four copies.
8
+ *
9
+ * import { startEmbers } from '.../midgard/design-system/ember.js'
10
+ * const stop = startEmbers(document.querySelector('[data-ember]'))
11
+ *
12
+ * Or simply mark an element and let it find its own:
13
+ *
14
+ * <canvas data-ember></canvas>
15
+ * <script type="module" src="/ember.js"></script>
16
+ *
17
+ * It is decorative: the canvas is given `pointer-events: none` and
18
+ * `aria-hidden`, and a reader who has asked their machine for less motion gets a
19
+ * still, single-frame hearth instead of an animation.
20
+ */
21
+
22
+ const EMBERS = 30;
23
+ const HAZE = 6;
24
+ const EMBER_RGB = '236,166,84';
25
+ const HAZE_RGB = '214,138,64';
26
+
27
+ /** Draw one still frame — also the whole effect under reduced motion. */
28
+ function paint(ctx, W, H, embers, haze) {
29
+ ctx.clearRect(0, 0, W, H);
30
+ for (const ha of haze) {
31
+ const g = ctx.createRadialGradient(ha.x, ha.y, 0, ha.x, ha.y, ha.r);
32
+ g.addColorStop(0, `rgba(${HAZE_RGB},0.030)`);
33
+ g.addColorStop(1, 'rgba(0,0,0,0)');
34
+ ctx.fillStyle = g;
35
+ ctx.beginPath();
36
+ ctx.arc(ha.x, ha.y, ha.r, 0, 6.283);
37
+ ctx.fill();
38
+ }
39
+ for (const e of embers) {
40
+ ctx.fillStyle = `rgba(${EMBER_RGB},${e.a.toFixed(3)})`;
41
+ ctx.beginPath();
42
+ ctx.arc(e.x, e.y, e.r, 0, 6.283);
43
+ ctx.fill();
44
+ }
45
+ }
46
+
47
+ function seed(W, H) {
48
+ const embers = [];
49
+ for (let i = 0; i < EMBERS; i++) {
50
+ embers.push({
51
+ x: Math.random() * W,
52
+ y: H + Math.random() * H * 0.3 - 6,
53
+ r: 1.0 + Math.random() * 1.6,
54
+ v: 0.14 + Math.random() * 0.34,
55
+ ph: Math.random() * 6.28,
56
+ sp: 0.6 + Math.random() * 1.4,
57
+ a: 0.26,
58
+ });
59
+ }
60
+ const haze = [];
61
+ for (let i = 0; i < HAZE; i++) {
62
+ haze.push({ x: Math.random() * W, y: H * 0.3 + Math.random() * H * 0.7, r: 130 + Math.random() * 170, v: (Math.random() - 0.5) * 0.15 });
63
+ }
64
+ return { embers, haze };
65
+ }
66
+
67
+ /**
68
+ * Start the hearth on <host>. Returns a stop function. Safe to call on a host
69
+ * that has no box yet (it simply draws nothing until it has one).
70
+ */
71
+ export function startEmbers(host) {
72
+ if (!host) return () => {};
73
+ const canvas = host.tagName === 'CANVAS' ? host : document.createElement('canvas');
74
+ if (canvas !== host) {
75
+ canvas.style.position = 'absolute';
76
+ canvas.style.inset = '0';
77
+ host.appendChild(canvas);
78
+ }
79
+ canvas.setAttribute('aria-hidden', 'true');
80
+ canvas.style.pointerEvents = 'none';
81
+
82
+ const ctx = canvas.getContext('2d');
83
+ if (!ctx) return () => {};
84
+
85
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
86
+ let W = 0;
87
+ let H = 0;
88
+ let t = 0;
89
+ let raf = 0;
90
+ let { embers, haze } = seed(1, 1);
91
+
92
+ const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;
93
+
94
+ function size() {
95
+ const rect = canvas.getBoundingClientRect();
96
+ W = rect.width || canvas.clientWidth || 0;
97
+ H = rect.height || canvas.clientHeight || 0;
98
+ canvas.width = Math.max(1, W * dpr);
99
+ canvas.height = Math.max(1, H * dpr);
100
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
101
+ }
102
+
103
+ function reseed() {
104
+ ({ embers, haze } = seed(W, H));
105
+ if (reduced) {
106
+ for (const e of embers) e.a = 0.3 + 0.4 * Math.random();
107
+ paint(ctx, W, H, embers, haze);
108
+ }
109
+ }
110
+
111
+ function frame() {
112
+ t += 0.016;
113
+ for (const ha of haze) {
114
+ ha.x += ha.v;
115
+ if (ha.x < -ha.r) ha.x = W + ha.r;
116
+ if (ha.x > W + ha.r) ha.x = -ha.r;
117
+ }
118
+ for (const e of embers) {
119
+ e.y -= e.v;
120
+ e.x += Math.sin(t * e.sp + e.ph) * 0.18;
121
+ if (e.y < -6) {
122
+ e.y = H + 6;
123
+ e.x = Math.random() * W;
124
+ }
125
+ e.a = 0.26 + 0.4 * (0.5 + 0.5 * Math.sin(t * e.sp * 2 + e.ph));
126
+ }
127
+ paint(ctx, W, H, embers, haze);
128
+ }
129
+
130
+ function loop() {
131
+ frame();
132
+ raf = requestAnimationFrame(loop);
133
+ }
134
+
135
+ size();
136
+ reseed();
137
+ if (!reduced) raf = requestAnimationFrame(loop);
138
+
139
+ const onResize = () => {
140
+ size();
141
+ reseed();
142
+ };
143
+ window.addEventListener('resize', onResize);
144
+
145
+ // The hearth warms a *container*, and containers change without a window
146
+ // resize (a split pane, a toggled sidebar). Watch the host: re-size and
147
+ // re-seed only when its dimensions actually changed, never per-frame.
148
+ let ro = null;
149
+ if (typeof ResizeObserver !== 'undefined' && canvas.parentElement) {
150
+ const host = canvas.parentElement;
151
+ let lastW = 0;
152
+ let lastH = 0;
153
+ ro = new ResizeObserver((entries) => {
154
+ const e = entries[0];
155
+ if (!e) return;
156
+ const w = e.contentRect?.width ?? e.contentBoxSize?.[0]?.inlineSize ?? 0;
157
+ const h = e.contentRect?.height ?? e.contentBoxSize?.[0]?.blockSize ?? 0;
158
+ if (Math.abs(w - lastW) > 0.5 || Math.abs(h - lastH) > 0.5) {
159
+ lastW = w;
160
+ lastH = h;
161
+ onResize();
162
+ }
163
+ });
164
+ ro.observe(host);
165
+ }
166
+
167
+ return () => {
168
+ if (raf) cancelAnimationFrame(raf);
169
+ window.removeEventListener('resize', onResize);
170
+ if (ro) ro.disconnect();
171
+ };
172
+ }
173
+
174
+ /** Every element marked `data-ember` lights its own hearth. */
175
+ export function startAllEmbers(root = document) {
176
+ return Array.from(root.querySelectorAll('[data-ember]')).map(startEmbers);
177
+ }
178
+
179
+ if (typeof document !== 'undefined') {
180
+ const boot = () => startAllEmbers();
181
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
182
+ else boot();
183
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Algiz — Reviews / protection">
2
+ <title>Algiz — Reviews / protection</title>
3
+ <path d="M12 20V10 M12 10L5 3 M12 10L19 3" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Ansuz — Forge">
2
+ <title>Ansuz — Forge</title>
3
+ <path d="M12 4V20 M12 9L7 4 M12 9L17 4" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Berkanan — Profile">
2
+ <title>Berkanan — Profile</title>
3
+ <path d="M9 4V20 M9 4L17 8V14L9 18" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Dagaz — Processes">
2
+ <title>Dagaz — Processes</title>
3
+ <path d="M5 5L19 19 M19 5L5 19 M5 5V19 M19 5V19" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Ehwaz — Runtime">
2
+ <title>Ehwaz — Runtime</title>
3
+ <path d="M4 20V8L12 14L20 8V20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Fehu — Fleet">
2
+ <title>Fehu — Fleet</title>
3
+ <path d="M12 20V4 M12 8L6 4 M12 8L18 4" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Gjallarhorn — tunnel">
2
+ <title>Gjallarhorn — tunnel</title>
3
+ <path d="M6 5L18 19 M18 5L6 19" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Gungnir — skills">
2
+ <title>Gungnir — skills</title>
3
+ <path d="M7 20V4 M7 6L17 12L7 18" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Heimdall — auth">
2
+ <title>Heimdall — auth</title>
3
+ <path d="M7 4V20 M17 4V20 M7 12H17" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Ingwaz — Well">
2
+ <title>Ingwaz — Well</title>
3
+ <path d="M12 4L20 12L12 20L4 12Z" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Jera — Cron">
2
+ <title>Jera — Cron</title>
3
+ <path d="M6 20L12 12L6 4 M18 4L12 12L18 20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Kaunan — OmniChat">
2
+ <title>Kaunan — OmniChat</title>
3
+ <path d="M18 4L6 12L18 20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Midgard — shared">
2
+ <title>Midgard — shared</title>
3
+ <path d="M4 12H20 M12 4V20 M6 6L18 18 M18 6L6 18" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Mimirsbrunn — memory">
2
+ <title>Mimirsbrunn — memory</title>
3
+ <path d="M12 4L20 12L12 20L4 12Z" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Othala — inheritance, the hall">
2
+ <title>Othala — inheritance, the hall</title>
3
+ <path d="M12 4L19 9L12 14L5 9Z M9 12L7 20 M15 12L17 20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Raidho — Runes">
2
+ <title>Raidho — Runes</title>
3
+ <path d="M8 20V4H14L18 8L14 12H8 M14 12L18 20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Ratatoskr — A2A bus">
2
+ <title>Ratatoskr — A2A bus</title>
3
+ <path d="M9 4V20 M9 4L17 8V14L9 18" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Sowilo — the sun">
2
+ <title>Sowilo — the sun</title>
3
+ <path d="M16 4L8 4L16 20L8 20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Tiwaz — Tasks">
2
+ <title>Tiwaz — Tasks</title>
3
+ <path d="M12 20V4 M12 4L6 11 M12 4L18 11" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Utgard — sandbox">
2
+ <title>Utgard — sandbox</title>
3
+ <path d="M8 20V8C8 5 16 5 16 8V20" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Valhalla — processes">
2
+ <title>Valhalla — processes</title>
3
+ <path d="M7 20V4 M7 4L17 8L7 12" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square" stroke-linejoin="miter" role="img" aria-label="Yggdrasil — worktrees">
2
+ <title>Yggdrasil — worktrees</title>
3
+ <path d="M6 20L12 12L6 4 M18 4L12 12L18 20" />
4
+ </svg>
@@ -0,0 +1,59 @@
1
+ # Rune-Glyph Icon Map
2
+
3
+ **Runecoded, not emoji.** Every icon in Ymir is an Elder Futhark rune drawn as a
4
+ 24px stroke glyph, colored by `currentColor`. Emoji are never used as icons.
5
+
6
+ ## Set
7
+
8
+ The glyphs live in `midgard/design-system/icons/*.svg` (24×24 viewBox; render at
9
+ 16px or 24px). Stroke uses `currentColor`, square caps, miter joins — the chisel
10
+ bevel. The primary seat may add a 1px inner bevel via the `--ymir-bevel` token.
11
+
12
+ ```
13
+ icons[21]{glyph,name,unicode,figure,used_for}:
14
+ "fehu","Fehu","ᚠ","Ymir (cattle/wealth)","Fleet — the herd of agents"
15
+ "tiwaz","Tiwaz","ᛏ","Týr (justice)","Tasks — the A2A lifecycle board"
16
+ "ingwaz","Ingwaz","ᛜ","Ing (the well)","Well — Mimirsbrunn recall"
17
+ "raidho","Raidho","ᚱ","ride/journey","Runes — the ledger (a road carved)"
18
+ "othala","Othala","ᛟ","inheritance, the hall","Sessrúmnir — the seat that holds the cloth"
19
+ "algiz","Algiz","ᛉ","protection","Reviews / Glitnir — the protective gate; the Ymir emblem"
20
+ "dagaz","Dagaz","ᛞ","day/dawn","Processes — Valhalla supervision"
21
+ "sowilo","Sowilo","ᛊ","sun","Files — Skrymir (light on the tree)"
22
+ "kaunan","Kaunan","ᚴ","torch","OmniChat — Kaia's flame"
23
+ "ansuz","Ansuz","ᚨ","Odin's breath","Óðrerir — the mead of poetry, the Live Hall"
24
+ "ehwaz","Ehwaz","ᛖ","horse/journey","Runtime — the seat and its transit"
25
+ "jera","Jera","ᛃ","year/harvest","Cron — the Nornir's seasons"
26
+ "berkana","Berkanan","ᛒ","birch","Profile — the self"
27
+ "midgard","Midgard","—","the shared world","shared workspace / cross-tenant assets"
28
+ "utgard","Utgard","ᚢ","the outer realm","Utgard — the sandbox barrier"
29
+ "yggdrasil","Yggdrasil","ᛃ","the world-tree","Yggdrasil — worktrees"
30
+ "ratatoskr","Ratatoskr","ᛒ","the squirrel","Ratatoskr — the A2A bus"
31
+ "mimirsbrunn","Mimirsbrunn","ᛜ","the well of wisdom","Mimirsbrunn — memory"
32
+ "valhalla","Wunjo","ᚹ","joy","Valhalla — process health (the glyph ᚹ is drawn here)"
33
+ "gungnir","Gungnir","ᚦ","Odin's spear","Gungnir — skill synthesis"
34
+ "heimdall","Heimdall","ᚺ","the watchman","Heimdall — auth / the gate"
35
+ "gjallarhorn","Gjallarhorn","ᚷ","the horn","Gjallarhorn — the tunnel and its call"
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ - Reference a glyph as an inline `<svg>` (copy the path) or `<img src>`; stroke
41
+ with `currentColor`, never a raw hex.
42
+ - Render at **24px** in panels, **16px** inline in tables and chips.
43
+ - A status glyph is always paired with **glyph + colour + text** — never
44
+ colour-only state.
45
+ - The active realm/accent may tint glyphs via `--realm-tint`.
46
+
47
+ ## Verification
48
+
49
+ ```bash
50
+ ls midgard/design-system/icons/*.svg | wc -l # 21
51
+ # no emoji used as icons in the UI:
52
+ rg -n "[\x{1F300}-\x{1FAFF}\x{2600}-\x{27BF}]" apps/hlidskjalf/src || echo "no emoji icons"
53
+ ```
54
+
55
+ ## Maintaining this
56
+
57
+ - **Owner:** Brokk. **Tokens:** `midgard/design-system/tokens.css`.
58
+ - Add a rune here and its glyph in `icons/` in the same pass; keep the count in the
59
+ `icons[N]` block true (Galdr TOON check: `.agents/skills/galdr-ymirsystem/scripts/toon-check.py`).
@@ -0,0 +1,78 @@
1
+ # The Rune Library
2
+
3
+ **Runecoded, not emoji.** Every icon in Ymir is an Elder Futhark rune drawn as a
4
+ 24×24 stroke glyph, coloured by `currentColor` (or the app's house tint). This is
5
+ the library: the full futhark, what each rune means, and where Ymir uses it.
6
+
7
+ - **Glyphs:** `midgard/design-system/icons/*.svg` (24×24 viewBox, stroke 1.8,
8
+ square caps, miter joins — the chisel bevel).
9
+ - **Names in code:** the file name is the rune's name, lower-case (`ehwaz.svg`).
10
+ - **Usage rules:** inline `<svg>` or `<img src>`; stroke with `currentColor`,
11
+ never a raw hex; 24px in panels, 16px inline; a status glyph is always paired
12
+ with **glyph + colour + text**, never colour alone.
13
+ - **The app runes:** five UI surfaces, five different glyphs — see the foot.
14
+
15
+ ## The futhark
16
+
17
+ ```
18
+ runes[24]{glyph,name,sound,meaning,ymir_use,file}:
19
+ "ᚠ","Fehu","f","cattle, wealth","Fleet — the herd of agents","fehu"
20
+ "ᚢ","Uruz","u","aurochs, strength","Utgard — the sandbox barrier","utgard"
21
+ "ᚦ","Thurisaz","th","thorn, giant","Gungnir — skill synthesis","gungnir"
22
+ "ᚨ","Ansuz","a","Odin's breath","Óðrerir — the mead of poetry, the Live Hall","ansuz"
23
+ "ᚱ","Raido","r","ride, journey","Runes — the ledger (a road carved)","raidho"
24
+ "ᚲ","Kaunan","k","torch, ulcer","Smíðja's eye — the trace of the forge, OmniChat's flame","kaunan"
25
+ "ᚷ","Gebo","g","gift","Gjallarhorn — the tunnel and its call","gjallarhorn"
26
+ "ᚹ","Wunjo","w","joy","Valhalla — the hall of the supervisor","valhalla"
27
+ "ᚺ","Hagall","h","hail","Heimdall — auth, the gate","heimdall"
28
+ "ᚾ","Naudiz","n","need, constraint","(no glyph yet)","—"
29
+ "ᛁ","Isaz","i","ice","(no glyph yet)","—"
30
+ "ᛃ","Jera","j","year, harvest","Nornir — the seasons; Yggdrasil — the world-tree's cycle","jera"
31
+ "ᛇ","Eihwaz","ï","yew","(no glyph yet)","—"
32
+ "ᛈ","Perthro","p","lot, fate","(no glyph yet)","—"
33
+ "ᛉ","Algiz","z","protection","Glitnir's reviews — the protective gate; **the Ymir emblem**","algiz"
34
+ "ᛊ","Sowilo","s","sun","Skrymir — files (light on the tree)","sowilo"
35
+ "ᛏ","Tiwaz","t","Týr, justice","Tasks — the A2A lifecycle board","tiwaz"
36
+ "ᛒ","Berkanan","b","birch","Profile — the self; Ratatoskr — the messenger","berkana"
37
+ "ᛖ","Ehwaz","e","horse","Runtime — the seat and its transit; Hlidskjalf","ehwaz"
38
+ "ᛗ","Mannaz","m","man, self","(no glyph yet)","—"
39
+ "ᛚ","Laguz","l","water, flow","(no glyph yet)","—"
40
+ "ᛜ","Ingwaz","ng","Ing, the seed","Mimirsbrunn — the well of wisdom","ingwaz"
41
+ "ᛞ","Dagaz","d","day, dawn","Valhalla — process health","dagaz"
42
+ "ᛟ","Othala","o","inheritance, estate","(no glyph yet)","—"
43
+ ```
44
+
45
+ **Twenty-one of the twenty-four are drawn; three are not yet:** Naudiz, Isaz,
46
+ Eihwaz, Perthro, Mannaz, Laguz and Othala have no file — add the glyph and its
47
+ row in `icons.md` in the same pass.
48
+
49
+ **Every file is a rune.** It was not always so: eight files used to be *drawings*
50
+ of the thing they were named after (a hall, a horn, a shield, a spear, a well, a
51
+ squirrel, a gate, a world-tree) while `icons.md` claimed a rune for each. Corrected
52
+ 2026-09-16 — Gungnir took Thurisaz, since Gebo was already Gjallarhorn's.
53
+
54
+ ## The app runes
55
+
56
+ Five UI surfaces, five different glyphs — no app borrows another's rune:
57
+
58
+ ```
59
+ app_runes[5]{app,glyph,rune,house_tint}:
60
+ "Hlidskjalf","ᛖ","ehwaz — the seat","#c9973f bronze (ymirlabs)"
61
+ "Hlidskjalf Mobile","ᚱ","raidho — the road, the seat carried","#c9973f bronze"
62
+ "Óðrerir","ᚨ","ansuz — Odin's breath, the mead of poetry","#c9973f bronze (ymirlabs)"
63
+ "Sessrúmnir","ᛟ","othala — inheritance, the hall that holds what is kept","#c9973f bronze (ymirlabs)"
64
+ "Smíðja (visualizer)","ᚲ","kaunan — the torch, the forge's eye","#f59e0b amber (brokkforge)"
65
+ ```
66
+
67
+ Minted and installed by `bin/design-icon.sh` (`list` · `mint --all` · `install`):
68
+ the icon into the user's icon theme, the `.desktop` entry into their applications
69
+ dir, so every app is dockable and carries the house mark.
70
+
71
+ ## Adding a rune
72
+
73
+ 1. Draw it in `icons/<name>.svg` — 24×24, stroke 1.8, square caps, miter joins,
74
+ `stroke="currentColor"`, a `<title>` naming the rune and its use.
75
+ 2. Add its row to `icons.md` **and** to the futhark table above, in the same
76
+ change. Keep both counts true (the TOON check counts rows).
77
+ 3. If an app should wear it, add the app to `bin/design-icon.sh`'s table and run
78
+ `mint --all && install`.