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 +1 -1
- package/src/kits/os/theme.css +38 -5
- package/src/shell.js +173 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
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",
|
package/src/kits/os/theme.css
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|