anentrypoint-design 0.0.420 → 0.0.422

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.420",
3
+ "version": "0.0.422",
4
4
  "description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
@@ -415,7 +415,12 @@ html, body {
415
415
  .app-text-cursor { animation: app-cursor-blink 0.5s step-end infinite; }
416
416
  @keyframes app-cursor-blink { 50% { opacity: 0; } }
417
417
  }
418
- @media (max-width: 1023px) and (min-width: 768px) {
418
+ /* (min-height: 501px): short-landscape windows (<501px tall) in this width
419
+ * band are on the mobile contract, not tablet — see the matching guard on
420
+ * the .wm-root tablet-inset rule below. Without the guard this block showed
421
+ * the side-rail and rail-offset the menubar/taskbar while the mobile band
422
+ * simultaneously tried to lay out rail-less full-screen windows. */
423
+ @media (max-width: 1023px) and (min-width: 768px) and (min-height: 501px) {
419
424
  .os-side-rail { display: flex; }
420
425
  .ds-247420 .wm-root { left: calc(var(--os-rail-w) + env(safe-area-inset-left, 0px)); }
421
426
  .os-menubar { padding-left: calc(var(--os-rail-w) + env(safe-area-inset-left, 0px) + var(--space-2, 8px)); }
@@ -563,8 +568,16 @@ html, body {
563
568
  /* With bars overlaying the wm-root, a maximized window's 34px titlebar would
564
569
  * sit exactly under the 34px menubar (untappable) and its body under the
565
570
  * taskbar; keep maximized windows between the bars on desktop/tablet. The
566
- * mobile band re-asserts its own contract in the responsive layer below. */
567
- @media (min-width: 768px) {
571
+ * mobile band re-asserts its own contract in the responsive layer below.
572
+ * The (min-height: 501px) guard keeps this rule out of the short-landscape
573
+ * mobile band (768-1023 wide AND <500 tall): the mobile .wm-root already
574
+ * carries the bar-inset there, so this between-the-bars geometry used to
575
+ * DOUBLE-inset maximized windows — a restored wm-max window measured
576
+ * y=104/h=240 instead of y=52/h=344 at 782x448 (witnessed live on thebird
577
+ * 2026-07-29). Guarding here (same idiom as the tablet-band rules below)
578
+ * instead of overriding inside the mobile band keeps the !important
579
+ * ratchet flat — no new declaration was needed. */
580
+ @media (min-width: 768px) and (min-height: 501px) {
568
581
  /* LOAD-BEARING !important: wm.css declares `.wm-win.wm-max{top:0!important;
569
582
  * height:100%!important}` (wm.css:137-143). Only another !important can beat
570
583
  * an !important, regardless of specificity. */
@@ -640,6 +653,12 @@ html.ds-247420 { touch-action: pan-x pan-y; overscroll-behavior: none; -webkit-t
640
653
  color: var(--os-fg);
641
654
  min-width: 280px;
642
655
  box-shadow: var(--shadow-overlay);
656
+ /* Uncapped, the switcher grew one row per open window with no bound —
657
+ * witnessed live at 27 windows on a 448px-tall viewport: the overlay
658
+ * measured y=-344 / h=1136, its top rows unreachable above the screen.
659
+ * Cap to the viewport between the bars and scroll the overflow. */
660
+ max-height: calc(100dvh - (2 * var(--os-bar-h, 34px)) - 24px);
661
+ overflow-y: auto;
643
662
  }
644
663
  .ds-247420 .wm-switcher-item {
645
664
  padding: var(--space-2) var(--space-2-75);
@@ -1488,6 +1507,11 @@ html.ds-247420 { touch-action: pan-x pan-y; overscroll-behavior: none; -webkit-t
1488
1507
  border-radius: 0;
1489
1508
  box-shadow: none;
1490
1509
  }
1510
+ /* A maximized window on the mobile band is just the full-screen window the
1511
+ * rule above already describes — the desktop/tablet `.wm-win.wm-max`
1512
+ * between-the-bars rule is kept out of this band by its own
1513
+ * (min-height: 501px) guard (see its comment above), so no override is
1514
+ * needed here and the !important ratchet stays flat. */
1491
1515
  /* titlebar must host a 44px close button -> 44px bar (beats the 34px brand
1492
1516
  * clamp by later source order at equal specificity) */
1493
1517
  .ds-247420 .wm-bar {
@@ -1529,8 +1553,17 @@ html.ds-247420 { touch-action: pan-x pan-y; overscroll-behavior: none; -webkit-t
1529
1553
  * the scoped inset:0; re-asserted here at equal (0,2,0) specificity but later
1530
1554
  * source order, which is enough now that the earlier rule is also scoped.
1531
1555
  * `inset` alone carries the left offset — the separate `left` declaration it
1532
- * used to be paired with was redundant with this shorthand. --- */
1533
- @media (min-width: 768px) and (max-width: 1023px) {
1556
+ * used to be paired with was redundant with this shorthand.
1557
+ * The (min-height: 501px) guard excludes the short-landscape overlap band
1558
+ * (768-1023 wide AND <500 tall): both this query and the mobile band's
1559
+ * (max-height: 500px)+(orientation: landscape) match there, and this rule —
1560
+ * later in source order — used to override the mobile bar-inset, dropping
1561
+ * .wm-root to top:0 so full-screen windows covered the menubar and their
1562
+ * close buttons landed under it occluded and untappable (witnessed live on
1563
+ * thebird at 782x448: elementFromPoint on the chat close button hit
1564
+ * .os-clock, menubar z 601 over window z 102). Short-landscape is the
1565
+ * phone/mobile contract, not tablet. --- */
1566
+ @media (min-width: 768px) and (max-width: 1023px) and (min-height: 501px) {
1534
1567
  .ds-247420 .wm-root {
1535
1568
  inset: 0 0 0 var(--os-rail-w);
1536
1569
  }
package/src/shell.js ADDED
@@ -0,0 +1,173 @@
1
+ // A real command interpreter for the terminal kit.
2
+ //
3
+ // The kit used to push '(stub) ran: <input>' into an array and render it -- the
4
+ // word "stub" was visible to users, and nothing executed. This module is the
5
+ // execution half: a virtual filesystem plus a command table, both synchronous
6
+ // and dependency-free, so the terminal surface has something genuine to drive.
7
+ //
8
+ // Deliberately NOT a real OS. Commands operate on an in-memory tree; there is
9
+ // no network, no eval, and no host access, so a demo page cannot be turned into
10
+ // an exfiltration surface by typing into it. Unknown input reports a real
11
+ // not-found error the way a shell does, rather than echoing success.
12
+
13
+ const FS = {
14
+ 'readme.md': 'the 247420 design system\n\nan editorial component library. run `ls` to look around,\n`help` for the command list.\n',
15
+ 'colors_and_type.css': '/* the token bible: --acid, --ink, --paper, the --fs-* scale */\n',
16
+ src: {
17
+ 'components.js': '// barrel over src/components/<group>.js\n',
18
+ 'bootstrap.js': '// mountKit() -- every kit boots through here\n',
19
+ css: { 'app-shell.css': '/* @import barrel over app-shell/*.css */\n' },
20
+ },
21
+ ui_kits: {
22
+ 'terminal/': null,
23
+ 'dashboard/': null,
24
+ 'file-browser/': null,
25
+ },
26
+ };
27
+
28
+ function nodeAt(path) {
29
+ let cur = FS;
30
+ for (const seg of path) {
31
+ if (!cur || typeof cur !== 'object') return undefined;
32
+ cur = cur[seg];
33
+ }
34
+ return cur;
35
+ }
36
+
37
+ const isDir = (n) => n && typeof n === 'object';
38
+
39
+ // Resolve a user-typed path against cwd, honouring . and .. and a leading /.
40
+ function resolvePath(cwd, raw) {
41
+ const parts = String(raw).split('/').filter(Boolean);
42
+ const out = String(raw).startsWith('/') ? [] : cwd.slice();
43
+ for (const p of parts) {
44
+ if (p === '.') continue;
45
+ if (p === '..') out.pop();
46
+ else out.push(p);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ const promptPath = (cwd) => '~/' + cwd.join('/');
52
+
53
+ // Each command returns an array of {kind, text} lines, matching the six line
54
+ // kinds the kit's Line() renderer already knows: cmt, cmd, out, ok, warn, log.
55
+ const COMMANDS = {
56
+ help(_args, ctx) {
57
+ const names = Object.keys(COMMANDS).sort();
58
+ return [
59
+ { kind: 'cmt', text: '# available commands' },
60
+ ...names.map((n) => ({ kind: 'out', text: n.padEnd(10) + COMMANDS[n].desc })),
61
+ { kind: 'out', text: '' },
62
+ { kind: 'out', text: 'up/down walks history, tab completes, ctrl+l or clear wipes the screen.' },
63
+ ];
64
+ },
65
+ ls(args, ctx) {
66
+ const target = args[0] ? resolvePath(ctx.cwd, args[0]) : ctx.cwd;
67
+ const node = nodeAt(target);
68
+ if (node === undefined) return [{ kind: 'warn', text: 'ls: ' + args[0] + ': no such file or directory' }];
69
+ if (!isDir(node)) return [{ kind: 'out', text: args[0] }];
70
+ const keys = Object.keys(node).sort();
71
+ if (!keys.length) return [{ kind: 'out', text: '(empty)' }];
72
+ return keys.map((k) => ({ kind: 'out', text: isDir(node[k]) ? k + '/' : k }));
73
+ },
74
+ cd(args, ctx) {
75
+ if (!args[0] || args[0] === '~') { ctx.cwd.length = 0; return []; }
76
+ const next = resolvePath(ctx.cwd, args[0]);
77
+ const node = nodeAt(next);
78
+ if (node === undefined) return [{ kind: 'warn', text: 'cd: ' + args[0] + ': no such file or directory' }];
79
+ if (!isDir(node)) return [{ kind: 'warn', text: 'cd: ' + args[0] + ': not a directory' }];
80
+ ctx.cwd.length = 0;
81
+ next.forEach((s) => ctx.cwd.push(s));
82
+ return [];
83
+ },
84
+ cat(args, ctx) {
85
+ if (!args[0]) return [{ kind: 'warn', text: 'cat: missing operand' }];
86
+ const node = nodeAt(resolvePath(ctx.cwd, args[0]));
87
+ if (node === undefined) return [{ kind: 'warn', text: 'cat: ' + args[0] + ': no such file or directory' }];
88
+ if (isDir(node)) return [{ kind: 'warn', text: 'cat: ' + args[0] + ': is a directory' }];
89
+ if (node === null) return [{ kind: 'out', text: '(binary or generated)' }];
90
+ return String(node).split('\n').map((text) => ({ kind: 'out', text }));
91
+ },
92
+ pwd(_args, ctx) { return [{ kind: 'out', text: promptPath(ctx.cwd) }]; },
93
+ echo(args) { return [{ kind: 'out', text: args.join(' ') }]; },
94
+ whoami() { return [{ kind: 'out', text: 'visitor@247420' }]; },
95
+ date() { return [{ kind: 'out', text: new Date().toString() }]; },
96
+ theme(args, ctx) {
97
+ const want = args[0];
98
+ if (want !== 'light' && want !== 'dark') {
99
+ return [{ kind: 'warn', text: 'theme: expected `light` or `dark`' }];
100
+ }
101
+ ctx.setTheme(want);
102
+ return [{ kind: 'ok', text: 'theme set to ' + want }];
103
+ },
104
+ clear(_args, ctx) { ctx.clear(); return []; },
105
+ };
106
+
107
+ COMMANDS.help.desc = 'list these commands';
108
+ COMMANDS.ls.desc = 'list directory contents';
109
+ COMMANDS.cd.desc = 'change directory';
110
+ COMMANDS.cat.desc = 'print a file';
111
+ COMMANDS.pwd.desc = 'print working directory';
112
+ COMMANDS.echo.desc = 'write arguments to output';
113
+ COMMANDS.whoami.desc = 'print the current user';
114
+ COMMANDS.date.desc = 'print the current date';
115
+ COMMANDS.theme.desc = 'switch light/dark';
116
+ COMMANDS.clear.desc = 'clear the scrollback';
117
+
118
+ // Split a command line into argv, honouring single and double quotes so
119
+ // `echo "two words"` is one argument rather than two.
120
+ export function tokenize(line) {
121
+ const out = [];
122
+ let cur = '';
123
+ let quote = null;
124
+ for (const ch of String(line)) {
125
+ if (quote) {
126
+ if (ch === quote) quote = null;
127
+ else cur += ch;
128
+ } else if (ch === '"' || ch === "'") {
129
+ quote = ch;
130
+ } else if (/\s/.test(ch)) {
131
+ if (cur) { out.push(cur); cur = ''; }
132
+ } else {
133
+ cur += ch;
134
+ }
135
+ }
136
+ if (cur) out.push(cur);
137
+ return out;
138
+ }
139
+
140
+ // Complete a partial word against command names (first token) or the current
141
+ // directory's entries (any later token). Returns the completed line, or the
142
+ // original when there is no unambiguous single match.
143
+ export function complete(line, cwd) {
144
+ const argv = tokenize(line);
145
+ const trailing = /\s$/.test(line);
146
+ const head = trailing ? '' : (argv[argv.length - 1] || '');
147
+ const pool = (argv.length <= 1 && !trailing)
148
+ ? Object.keys(COMMANDS)
149
+ : Object.keys(nodeAt(cwd) || {});
150
+ const hits = pool.filter((k) => k.startsWith(head));
151
+ if (hits.length !== 1) return line;
152
+ const base = trailing ? argv : argv.slice(0, -1);
153
+ return [...base, hits[0]].join(' ');
154
+ }
155
+
156
+ export const commandNames = () => Object.keys(COMMANDS).sort();
157
+
158
+ // Run one line. ctx carries { cwd, clear, setTheme } so commands can mutate the
159
+ // session without this module reaching into the DOM itself.
160
+ export function run(line, ctx) {
161
+ const argv = tokenize(line);
162
+ if (!argv.length) return [];
163
+ const [name, ...args] = argv;
164
+ const cmd = COMMANDS[name];
165
+ if (!cmd) {
166
+ return [{ kind: 'warn', text: name + ': command not found — try `help`' }];
167
+ }
168
+ try {
169
+ return cmd(args, ctx) || [];
170
+ } catch (err) {
171
+ return [{ kind: 'warn', text: name + ': ' + (err && err.message ? err.message : 'failed') }];
172
+ }
173
+ }