catherd-cli 1.4.0 → 2.0.0-beta.20261022113309
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/README.md +188 -169
- package/THIRD_PARTY_NOTICES.md +217 -35
- package/catalog/jev.json +27 -1
- package/catalog/models.json +56 -239
- package/package.json +6 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/commands/catherd.md +2 -2
- package/plugin/skills/catherd/SKILL.md +97 -356
- package/plugin/skills/catherd-setup/SKILL.md +21 -20
- package/src/adapters/all.ts +0 -4
- package/src/adapters/backend.ts +19 -4
- package/src/adapters/claude-code/index.ts +126 -14
- package/src/adapters/codex/events.ts +10 -0
- package/src/adapters/codex/index.ts +17 -5
- package/src/adapters/cursor/home.ts +1 -1
- package/src/adapters/cursor/index.ts +10 -4
- package/src/adapters/opencode/index.ts +38 -1
- package/src/cli.ts +27 -27
- package/src/domain/agents.ts +3 -1
- package/src/domain/bars.ts +33 -107
- package/src/domain/blocked.ts +72 -0
- package/src/domain/branch.ts +51 -0
- package/src/domain/brief.ts +106 -0
- package/src/domain/catalog.ts +69 -313
- package/src/domain/changes.ts +23 -1
- package/src/domain/cost.ts +19 -29
- package/src/domain/errors.ts +6 -2
- package/src/domain/fail-lines.ts +26 -0
- package/src/domain/failover.ts +37 -75
- package/src/domain/findings.ts +47 -0
- package/src/domain/gate-brief.ts +53 -0
- package/src/domain/hints.ts +13 -2
- package/src/domain/ids.ts +13 -2
- package/src/domain/jev.ts +47 -2
- package/src/domain/judge.ts +124 -0
- package/src/domain/lane.ts +118 -3
- package/src/domain/milestone-memory.ts +53 -0
- package/src/domain/notice.ts +32 -8
- package/src/domain/notify.ts +32 -0
- package/src/domain/plan-doc.ts +198 -0
- package/src/domain/plan-lint.ts +141 -0
- package/src/domain/profile-rules.ts +175 -138
- package/src/domain/profile.ts +140 -63
- package/src/domain/record.ts +68 -3
- package/src/domain/reply.ts +29 -0
- package/src/domain/role-prompts.ts +182 -56
- package/src/domain/role-scope.ts +76 -0
- package/src/domain/role-tools.ts +34 -0
- package/src/domain/roles.ts +2 -0
- package/src/domain/route.ts +90 -0
- package/src/domain/rules.ts +33 -0
- package/src/domain/run-events.ts +298 -0
- package/src/domain/run-machine.ts +668 -0
- package/src/domain/run-state.ts +1310 -0
- package/src/domain/scorecard.ts +197 -0
- package/src/domain/select.ts +229 -72
- package/src/domain/slopalytics.ts +72 -0
- package/src/domain/state.ts +5 -0
- package/src/domain/util.ts +22 -0
- package/src/domain/workspace.ts +130 -0
- package/src/entry/answer-command.ts +39 -0
- package/src/entry/catalog-command.ts +24 -165
- package/src/entry/check-bin.ts +17 -0
- package/src/entry/cli-kit.ts +18 -2
- package/src/entry/daemon-bin.ts +22 -0
- package/src/entry/deps.ts +2 -0
- package/src/entry/doctor-command.ts +20 -2
- package/src/entry/glyphs.ts +15 -2
- package/src/entry/host-arg.ts +28 -0
- package/src/entry/init-command.ts +20 -69
- package/src/entry/knowledge-command.ts +121 -5
- package/src/entry/lock-command.ts +103 -8
- package/src/entry/mcp/handshake.ts +6 -2
- package/src/entry/mcp/role-bin.ts +20 -0
- package/src/entry/mcp/role-server.ts +134 -0
- package/src/entry/mcp/server.ts +85 -21
- package/src/entry/mcp/session-tools.ts +149 -0
- package/src/entry/panel/openapi.ts +189 -0
- package/src/entry/panel/server.ts +410 -0
- package/src/entry/panel-command.ts +68 -0
- package/src/entry/pause-command.ts +67 -0
- package/src/entry/profile-command.ts +33 -18
- package/src/entry/role-cli-command.ts +159 -0
- package/src/entry/runs-command.ts +81 -27
- package/src/entry/scorecard-command.ts +187 -0
- package/src/entry/supervise-bin.ts +1 -1
- package/src/entry/supervise-command.ts +30 -3
- package/src/entry/workspace-command.ts +44 -0
- package/src/infra/boot-lock.ts +25 -0
- package/src/infra/delivery.ts +3 -0
- package/src/infra/dispatch-dir.ts +2 -0
- package/src/infra/env.ts +11 -3
- package/src/infra/git.ts +70 -0
- package/src/infra/heavy-lock.ts +133 -6
- package/src/infra/host-probe.ts +16 -0
- package/src/infra/launch.ts +63 -7
- package/src/infra/lock-activity.ts +88 -0
- package/src/infra/login-env.ts +48 -0
- package/src/infra/memory.ts +45 -0
- package/src/infra/ntfy.ts +40 -0
- package/src/infra/open-url.ts +42 -0
- package/src/infra/paths.ts +37 -2
- package/src/infra/role-mcp.ts +69 -0
- package/src/infra/slopalytics.ts +30 -0
- package/src/infra/store.ts +6 -1
- package/src/infra/supervisor.ts +107 -16
- package/src/services/admission.ts +133 -31
- package/src/services/backends.ts +4 -6
- package/src/services/budget.ts +26 -6
- package/src/services/capture.ts +1 -22
- package/src/services/catalog-service.ts +155 -254
- package/src/services/credentials.ts +15 -26
- package/src/services/daemon.ts +181 -0
- package/src/services/dispatch-service.ts +209 -17
- package/src/services/dispatches.ts +30 -2
- package/src/services/doctor-access.ts +8 -3
- package/src/services/doctor-backends.ts +1 -1
- package/src/services/doctor-checks.ts +47 -0
- package/src/services/doctor-docker.ts +221 -0
- package/src/services/doctor-role-mcp.ts +65 -0
- package/src/services/doctor.ts +72 -15
- package/src/services/end-push.ts +157 -0
- package/src/services/failover-rungs.ts +109 -0
- package/src/services/finalize.ts +24 -2
- package/src/services/gate-env.ts +217 -0
- package/src/services/gate-service.ts +231 -45
- package/src/services/global-install.ts +46 -13
- package/src/services/jev-service.ts +2 -4
- package/src/services/lane-edit.ts +154 -0
- package/src/services/lane-service.ts +291 -55
- package/src/services/milestone-check.ts +433 -0
- package/src/services/milestone-tree.ts +170 -0
- package/src/services/milestones.ts +167 -29
- package/src/services/notifier.ts +124 -4
- package/src/services/notify-service.ts +26 -0
- package/src/services/orchestrator-wait.ts +82 -0
- package/src/services/panel-setup.ts +116 -0
- package/src/services/pause.ts +161 -0
- package/src/services/peek.ts +79 -17
- package/src/services/plan-input.ts +151 -0
- package/src/services/ports.ts +60 -5
- package/src/services/preflight.ts +232 -24
- package/src/services/profile-service.ts +31 -3
- package/src/services/profile-store.ts +18 -6
- package/src/services/protocol.ts +123 -25
- package/src/services/provenance.ts +34 -89
- package/src/services/questions.ts +24 -0
- package/src/services/reconcile.ts +33 -1
- package/src/services/reentry.ts +7 -6
- package/src/services/role-access.ts +27 -0
- package/src/services/routing-service.ts +161 -42
- package/src/services/run-briefs.ts +477 -0
- package/src/services/run-client.ts +217 -0
- package/src/services/run-effects.ts +837 -0
- package/src/services/run-events-store.ts +174 -0
- package/src/services/run-evidence.ts +1 -1
- package/src/services/run-pin.ts +125 -0
- package/src/services/run-read.ts +221 -0
- package/src/services/run-report.ts +176 -0
- package/src/services/run-service.ts +204 -23
- package/src/services/run-store.ts +159 -18
- package/src/services/scorecard-metrics.ts +86 -0
- package/src/services/scorecard.ts +382 -0
- package/src/services/scratch.ts +60 -0
- package/src/services/sessions.ts +3 -1
- package/src/services/state.ts +21 -0
- package/src/services/summary.ts +76 -12
- package/src/services/tree-path.ts +27 -0
- package/src/services/verifier-brief.ts +23 -0
- package/src/services/verifier-step.ts +48 -0
- package/src/services/workspace-admission.ts +259 -0
- package/src/services/workspace-service.ts +259 -0
- package/src/services/workspace-store.ts +95 -0
- package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js +1 -0
- package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
- package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js +1 -0
- package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
- package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/web/dist/assets/index-B6GiwDkh.js +11 -0
- package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
- package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
- package/web/dist/assets/index-C5IbylvM.css +2 -0
- package/web/dist/assets/index-C5IbylvM.css.br +0 -0
- package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
- package/web/dist/assets/label-DFSYBc62.js +1 -0
- package/web/dist/assets/label-DFSYBc62.js.br +0 -0
- package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
- package/web/dist/assets/link-D8EcKZUt.js +1 -0
- package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
- package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
- package/web/dist/assets/login-DaN5HPPW.js +1 -0
- package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
- package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
- package/web/dist/assets/query-Cu--ccT3.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
- package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
- package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
- package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
- package/web/dist/assets/table-BOxXhqS4.js +1 -0
- package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
- package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
- package/web/dist/assets/time--9kSQbbp.js +1 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
- package/web/dist/index.html +15 -0
- package/catalog/ATTRIBUTION.md +0 -36
- package/catalog/scores.json +0 -3202
- package/catalog/sources.json +0 -97
- package/src/adapters/antigravity/events.ts +0 -131
- package/src/adapters/antigravity/home.ts +0 -46
- package/src/adapters/antigravity/index.ts +0 -320
- package/src/adapters/antigravity/models.ts +0 -37
- package/src/adapters/grok/events.ts +0 -112
- package/src/adapters/grok/home.ts +0 -132
- package/src/adapters/grok/index.ts +0 -339
- package/src/adapters/grok/models.ts +0 -48
- package/src/domain/calibration.ts +0 -125
- package/src/domain/sources.ts +0 -144
- package/src/entry/mcp/dispatch-tools.ts +0 -52
- package/src/entry/mcp/lane-tools.ts +0 -84
- package/src/entry/mcp/protocol-tools.ts +0 -57
- package/src/entry/mcp/run-tools.ts +0 -148
- package/src/entry/mcp/setup-tools.ts +0 -108
- package/src/entry/tui/commands.ts +0 -563
- package/src/entry/tui/effects.ts +0 -381
- package/src/entry/tui/fixtures.ts +0 -637
- package/src/entry/tui/profile-edits.ts +0 -146
- package/src/entry/tui/profile-tree.ts +0 -429
- package/src/entry/tui/providers/app.tsx +0 -162
- package/src/entry/tui/providers/boundary.tsx +0 -57
- package/src/entry/tui/providers/data.tsx +0 -160
- package/src/entry/tui/providers/keymap.tsx +0 -166
- package/src/entry/tui/providers/root.tsx +0 -45
- package/src/entry/tui/providers/theme.tsx +0 -17
- package/src/entry/tui/providers/toast.tsx +0 -77
- package/src/entry/tui/run.tsx +0 -114
- package/src/entry/tui/state.ts +0 -373
- package/src/entry/tui/stories.ts +0 -114
- package/src/entry/tui/text.ts +0 -130
- package/src/entry/tui/theme.ts +0 -140
- package/src/entry/tui/views/app.tsx +0 -208
- package/src/entry/tui/views/dialogs.tsx +0 -41
- package/src/entry/tui/views/profile-actions.ts +0 -331
- package/src/entry/tui/views/profiles.tsx +0 -520
- package/src/entry/tui/views/runs.tsx +0 -573
- package/src/entry/tui/views/save-dialog.tsx +0 -340
- package/src/entry/tui/views/status.tsx +0 -209
- package/src/entry/tui/widgets/chrome.tsx +0 -114
- package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
- package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
- package/src/entry/tui/widgets/dialog-select.tsx +0 -212
- package/src/entry/tui/widgets/dialog.tsx +0 -68
- package/src/entry/tui/widgets/line.tsx +0 -92
- package/src/entry/tui/widgets/list.tsx +0 -197
- package/src/entry/tui/widgets/spinner.tsx +0 -49
- package/src/entry/tui/widgets/toast.tsx +0 -62
- package/src/infra/sources/arena.ts +0 -87
- package/src/infra/sources/artificial-analysis.ts +0 -112
- package/src/infra/sources/cache.ts +0 -124
- package/src/infra/sources/csv.ts +0 -38
- package/src/infra/sources/epoch.ts +0 -82
- package/src/infra/sources/http.ts +0 -75
- package/src/infra/sources/litellm.ts +0 -58
- package/src/infra/sources/models-dev.ts +0 -59
- package/src/infra/sources/openrouter-endpoints.ts +0 -52
- package/src/infra/sources/openrouter-models.ts +0 -60
- package/src/infra/sources/rows.ts +0 -38
- package/src/infra/sources/vectara.ts +0 -69
- package/src/infra/sources/zip.ts +0 -48
- package/src/services/catalog-refresh.ts +0 -201
- package/src/services/doctor-sources.ts +0 -76
- package/src/services/runs-page.ts +0 -255
- package/src/services/source-derive.ts +0 -374
- package/src/services/source-sync.ts +0 -305
- package/src/services/standins.ts +0 -204
- package/src/services/treat-likes.ts +0 -97
package/src/entry/tui/text.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** Terminal columns a string takes (wide and zero-width characters counted as the terminal does). */
|
|
2
|
-
export const width = (s: string): number => Bun.stringWidth(s);
|
|
3
|
-
|
|
4
|
-
/** Cuts `s` at the end to `max` columns with an ellipsis; never in the middle (spec §9.3). */
|
|
5
|
-
export function truncateEnd(s: string, max: number, plain = false): string {
|
|
6
|
-
if (max <= 0) return "";
|
|
7
|
-
if (width(s) <= max) return s;
|
|
8
|
-
const more = plain ? "..." : "…";
|
|
9
|
-
const room = max - width(more);
|
|
10
|
-
if (room <= 0) return more.slice(0, max);
|
|
11
|
-
let out = "";
|
|
12
|
-
for (const ch of s) {
|
|
13
|
-
if (width(out + ch) > room) break;
|
|
14
|
-
out += ch;
|
|
15
|
-
}
|
|
16
|
-
return out + more;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** `1 change`, `2 changes`: a count with its noun, never `change(s)`. */
|
|
20
|
-
export const plural = (n: number, noun: string): string => `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
21
|
-
|
|
22
|
-
/** Pads with spaces to exactly `w` columns (truncating nothing: callers truncate first). */
|
|
23
|
-
export const padEnd = (s: string, w: number): string => s + " ".repeat(Math.max(0, w - width(s)));
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Wraps to `max` columns without losing a character: breaks at spaces, and inside a word only when the
|
|
27
|
-
* word alone is wider than a line (ids, paths and commands wrap; spec §9.3).
|
|
28
|
-
*/
|
|
29
|
-
export function wrap(s: string, max: number): string[] {
|
|
30
|
-
if (max <= 0) return [s];
|
|
31
|
-
const lines: string[] = [];
|
|
32
|
-
let line = "";
|
|
33
|
-
const push = () => {
|
|
34
|
-
lines.push(line);
|
|
35
|
-
line = "";
|
|
36
|
-
};
|
|
37
|
-
for (const word of s.split(" ")) {
|
|
38
|
-
const candidate = line ? `${line} ${word}` : word;
|
|
39
|
-
if (width(candidate) <= max) {
|
|
40
|
-
line = candidate;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (line) push();
|
|
44
|
-
let rest = word;
|
|
45
|
-
while (width(rest) > max) {
|
|
46
|
-
let head = "";
|
|
47
|
-
for (const ch of rest) {
|
|
48
|
-
if (head && width(head + ch) > max) break;
|
|
49
|
-
head += ch;
|
|
50
|
-
}
|
|
51
|
-
lines.push(head);
|
|
52
|
-
rest = rest.slice(head.length);
|
|
53
|
-
}
|
|
54
|
-
line = rest;
|
|
55
|
-
}
|
|
56
|
-
if (line || lines.length === 0) push();
|
|
57
|
-
return lines;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* `wrap` with a hanging indent: a line's leading spaces and list marker (`- `, `* `, `1. `) are kept on its first
|
|
62
|
-
* line, and its continuation lines are indented to match, so a wrapped bullet does not read as a new line.
|
|
63
|
-
*/
|
|
64
|
-
export function wrapHanging(s: string, max: number): string[] {
|
|
65
|
-
const lead = /^\s*(?:[-*+] |\d+[.)] )?/.exec(s)?.[0] ?? "";
|
|
66
|
-
const hang = width(lead);
|
|
67
|
-
if (!lead || max - hang < 1) return wrap(s, max);
|
|
68
|
-
const pad = " ".repeat(hang);
|
|
69
|
-
return wrap(s.slice(lead.length), max - hang).map((l, i) => (i === 0 ? lead : pad) + l);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** `04:12`, or `1:02:03` past an hour. */
|
|
73
|
-
export function clock(secs: number): string {
|
|
74
|
-
const s = Math.max(0, Math.floor(secs));
|
|
75
|
-
const h = Math.floor(s / 3600);
|
|
76
|
-
const mm = String(Math.floor(s / 60) % 60).padStart(2, "0");
|
|
77
|
-
const ss = String(s % 60).padStart(2, "0");
|
|
78
|
-
return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** "just now", "12s ago", "4m ago", "2h ago", "3d ago". */
|
|
82
|
-
export function ago(ms: number): string {
|
|
83
|
-
const s = Math.max(0, Math.floor(ms / 1000));
|
|
84
|
-
if (s < 2) return "just now";
|
|
85
|
-
if (s < 60) return `${s}s ago`;
|
|
86
|
-
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
87
|
-
if (s < 86_400) return `${Math.floor(s / 3600)}h ago`;
|
|
88
|
-
return `${Math.floor(s / 86_400)}d ago`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** `gpt-6-sol#medium` from `codex:gpt-6-sol#medium`: the backend goes, and a provider path keeps its last part. */
|
|
92
|
-
export function shortRung(rung: string): string {
|
|
93
|
-
const noBackend = rung.slice(rung.indexOf(":") + 1);
|
|
94
|
-
return noBackend.slice(noBackend.lastIndexOf("/") + 1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const ASCII: Record<string, string> = {
|
|
98
|
-
"—": "-",
|
|
99
|
-
"–": "-",
|
|
100
|
-
"·": "-",
|
|
101
|
-
"…": "...",
|
|
102
|
-
"⋯": "...",
|
|
103
|
-
"→": "->",
|
|
104
|
-
"←": "<-",
|
|
105
|
-
"↑": "^",
|
|
106
|
-
"↓": "v",
|
|
107
|
-
"●": "*",
|
|
108
|
-
"•": "*",
|
|
109
|
-
"◌": ".",
|
|
110
|
-
"✓": "+",
|
|
111
|
-
"✗": "x",
|
|
112
|
-
"▸": ">",
|
|
113
|
-
"▾": "v",
|
|
114
|
-
"┃": "|",
|
|
115
|
-
"─": "-",
|
|
116
|
-
"█": "#",
|
|
117
|
-
"░": ".",
|
|
118
|
-
"×": "x",
|
|
119
|
-
ω: "w",
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/** `s` with every character outside printable ASCII replaced, for --plain (spec §9.3). */
|
|
123
|
-
export function ascii(s: string): string {
|
|
124
|
-
let out = "";
|
|
125
|
-
for (const ch of s) {
|
|
126
|
-
const c = ch.codePointAt(0) ?? 0;
|
|
127
|
-
out += c >= 0x20 && c <= 0x7e ? ch : (ASCII[ch] ?? "?");
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
130
|
-
}
|
package/src/entry/tui/theme.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import type { State } from "../glyphs.ts";
|
|
2
|
-
|
|
3
|
-
/** Spec §9.3: the 16 semantic tokens every widget paints with. */
|
|
4
|
-
export const TOKENS = [
|
|
5
|
-
"text",
|
|
6
|
-
"muted",
|
|
7
|
-
"accent",
|
|
8
|
-
"accentText",
|
|
9
|
-
"surface",
|
|
10
|
-
"surfaceRaised",
|
|
11
|
-
"backdrop",
|
|
12
|
-
"border",
|
|
13
|
-
"success",
|
|
14
|
-
"warning",
|
|
15
|
-
"error",
|
|
16
|
-
"info",
|
|
17
|
-
"selection",
|
|
18
|
-
"selectionText",
|
|
19
|
-
"diffAdd",
|
|
20
|
-
"diffDel",
|
|
21
|
-
] as const;
|
|
22
|
-
export type Token = (typeof TOKENS)[number];
|
|
23
|
-
export type Palette = Record<Token, string>;
|
|
24
|
-
export type ThemeMode = "dark" | "light";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Accent ginger `#E8833A` on dark and `#B45309` on light (spec §9.3); every colour that carries text
|
|
28
|
-
* keeps 4.5:1 against both surfaces (test/entry/tui/theme.test.ts). The screen itself keeps the
|
|
29
|
-
* terminal's own background; `surface` is only what text on the accent fill contrasts with.
|
|
30
|
-
*/
|
|
31
|
-
export const PALETTES: Record<ThemeMode, Palette> = {
|
|
32
|
-
dark: {
|
|
33
|
-
text: "#EEEEEE",
|
|
34
|
-
muted: "#8A8A8A",
|
|
35
|
-
accent: "#E8833A",
|
|
36
|
-
accentText: "#0A0A0A",
|
|
37
|
-
surface: "#0A0A0A",
|
|
38
|
-
surfaceRaised: "#1E1E1E",
|
|
39
|
-
backdrop: "#000000",
|
|
40
|
-
border: "#484848",
|
|
41
|
-
success: "#7FD88F",
|
|
42
|
-
warning: "#F5A742",
|
|
43
|
-
error: "#E06C75",
|
|
44
|
-
info: "#56B6C2",
|
|
45
|
-
selection: "#E8833A",
|
|
46
|
-
selectionText: "#0A0A0A",
|
|
47
|
-
diffAdd: "#4FD6BE",
|
|
48
|
-
diffDel: "#E06C75",
|
|
49
|
-
},
|
|
50
|
-
light: {
|
|
51
|
-
text: "#1A1A1A",
|
|
52
|
-
muted: "#6B6B6B",
|
|
53
|
-
accent: "#B45309",
|
|
54
|
-
accentText: "#FFFFFF",
|
|
55
|
-
surface: "#FFFFFF",
|
|
56
|
-
surfaceRaised: "#F7F7F7",
|
|
57
|
-
backdrop: "#000000",
|
|
58
|
-
border: "#B8B8B8",
|
|
59
|
-
success: "#2F7D46",
|
|
60
|
-
warning: "#8A6116",
|
|
61
|
-
error: "#C22D33",
|
|
62
|
-
info: "#256F7B",
|
|
63
|
-
selection: "#B45309",
|
|
64
|
-
selectionText: "#FFFFFF",
|
|
65
|
-
diffAdd: "#1E725C",
|
|
66
|
-
diffDel: "#B42A3E",
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/** How this terminal is drawn: `plain` is ASCII only, `color` false paints nothing (NO_COLOR). */
|
|
71
|
-
export interface Ui {
|
|
72
|
-
plain: boolean;
|
|
73
|
-
color: boolean;
|
|
74
|
-
reducedMotion: boolean;
|
|
75
|
-
mode: ThemeMode;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
type Env = Record<string, string | undefined>;
|
|
79
|
-
|
|
80
|
-
/** The first locale variable that is set decides; with none set the terminal is taken to speak UTF-8. */
|
|
81
|
-
function utf8(env: Env): boolean {
|
|
82
|
-
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG;
|
|
83
|
-
return !locale || /utf-?8/i.test(locale);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Spec §9.3: `NO_COLOR` and `--plain` (ASCII glyphs, no colour). Reads only `rawArgs` and `env`, never
|
|
88
|
-
* the terminal, so it answers the same on every machine. OpenTUI downsamples true colour itself.
|
|
89
|
-
*/
|
|
90
|
-
export function detectUi(rawArgs: readonly string[], env: Env): Omit<Ui, "mode"> {
|
|
91
|
-
const dumb = env.TERM === "dumb";
|
|
92
|
-
const plain = rawArgs.includes("--plain") || env.TERM === "linux" || dumb || !utf8(env);
|
|
93
|
-
return {
|
|
94
|
-
plain,
|
|
95
|
-
color: !rawArgs.includes("--plain") && !dumb && !env.NO_COLOR,
|
|
96
|
-
reducedMotion: rawArgs.includes("--reduced-motion") || Boolean(env.CATHERD_REDUCED_MOTION),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** A token's colour, or undefined when the terminal gets no colour. */
|
|
101
|
-
export const paint = (ui: Ui, token: Token): string | undefined =>
|
|
102
|
-
ui.color ? PALETTES[ui.mode][token] : undefined;
|
|
103
|
-
|
|
104
|
-
export { glyph, GLYPH_NAMES, type Glyph, type State } from "../glyphs.ts";
|
|
105
|
-
|
|
106
|
-
/** Each state's colour token; the word travels with it. */
|
|
107
|
-
export const STATE_TOKEN: Record<State, Token> = {
|
|
108
|
-
ok: "success",
|
|
109
|
-
warn: "warning",
|
|
110
|
-
fail: "error",
|
|
111
|
-
skip: "muted",
|
|
112
|
-
info: "info",
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
export type Mood = "good" | "working" | "waiting" | "failed";
|
|
116
|
-
export const FACES: Record<Mood, string> = {
|
|
117
|
-
good: "=^.^=",
|
|
118
|
-
working: "=o.o=",
|
|
119
|
-
waiting: "=-.-=",
|
|
120
|
-
failed: "=x.x=",
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/** The conductor, for the init welcome and empty states only (spec §9.3). ASCII already. */
|
|
124
|
-
export function mascot(mood: Mood): [string, string, string] {
|
|
125
|
-
return [" /\\_/\\ .", `(${FACES[mood]})/`, ' (")(")'].map((l) => l.padEnd(9)) as [string, string, string];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** Spec §9.3 motion: a braille spinner at 80 ms, shown after 500 ms and kept at least 3 s. */
|
|
129
|
-
export const SPINNER = {
|
|
130
|
-
frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
131
|
-
plainFrames: ["|", "/", "-", "\\"],
|
|
132
|
-
still: ["⋯", "..."],
|
|
133
|
-
ms: 80,
|
|
134
|
-
showAfterMs: 500,
|
|
135
|
-
minShowMs: 3_000,
|
|
136
|
-
} as const;
|
|
137
|
-
|
|
138
|
-
/** Spec §9.3: dialogs are 60, 88 or 116 columns, clamped to the terminal. */
|
|
139
|
-
export const DIALOG_WIDTH = { medium: 60, large: 88, xlarge: 116 } as const;
|
|
140
|
-
export type DialogSize = keyof typeof DIALOG_WIDTH;
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { useTerminalDimensions } from "@opentui/react";
|
|
2
|
-
import { type CommandId, formatKeys } from "../commands.ts";
|
|
3
|
-
import { hereWord } from "../effects.ts";
|
|
4
|
-
import { useApp, useDialogHandler } from "../providers/app.tsx";
|
|
5
|
-
import { DataProvider, useData } from "../providers/data.tsx";
|
|
6
|
-
import { reachableCommands, useCommandLayer, useKeybinds, useKeymap } from "../providers/keymap.tsx";
|
|
7
|
-
import { useUi } from "../providers/theme.tsx";
|
|
8
|
-
import { currentDraft, isArmed, isSaving, TABS, totalDirty } from "../state.ts";
|
|
9
|
-
import type { Mood } from "../theme.ts";
|
|
10
|
-
import { Footer, Header, Tabs } from "../widgets/chrome.tsx";
|
|
11
|
-
import { plural } from "../text.ts";
|
|
12
|
-
import { Rule } from "../widgets/line.tsx";
|
|
13
|
-
import { Spinner, useDelayedPresence } from "../widgets/spinner.tsx";
|
|
14
|
-
import { ToastHost } from "../widgets/toast.tsx";
|
|
15
|
-
import { Behind, DialogHost } from "./dialogs.tsx";
|
|
16
|
-
import { playStory, STORIES } from "../stories.ts";
|
|
17
|
-
import { openNewProfile, openProfileList, useProfileDialogs } from "./profile-actions.ts";
|
|
18
|
-
import { ProfilesView } from "./profiles.tsx";
|
|
19
|
-
import { RunsView } from "./runs.tsx";
|
|
20
|
-
import { StatusView } from "./status.tsx";
|
|
21
|
-
|
|
22
|
-
/** Rows the chrome takes at any size: header, tabs, rule, rule, footer (spec §9.3: 19 of 24 rows left). */
|
|
23
|
-
const CHROME_ROWS = 5;
|
|
24
|
-
|
|
25
|
-
/** The palette's and help's rows, read from the commands a key could reach before the dialog opens. */
|
|
26
|
-
function useCommandDialogs() {
|
|
27
|
-
const app = useApp();
|
|
28
|
-
const keymap = useKeymap();
|
|
29
|
-
const keys = useKeybinds();
|
|
30
|
-
const ui = useUi();
|
|
31
|
-
const open = (kind: "palette" | "help") => {
|
|
32
|
-
const entries = reachableCommands(keymap).filter((c) =>
|
|
33
|
-
kind === "palette" ? c.palette : keys[c.id].length > 0 && !c.id.startsWith("dialog."),
|
|
34
|
-
);
|
|
35
|
-
app.dispatch({
|
|
36
|
-
type: "open",
|
|
37
|
-
dialog: {
|
|
38
|
-
kind: "select",
|
|
39
|
-
purpose: { type: kind },
|
|
40
|
-
title: kind === "palette" ? "Commands" : "Keyboard shortcuts",
|
|
41
|
-
empty: "no commands",
|
|
42
|
-
suggested: kind === "palette" ? entries.filter((c) => c.suggested).map((c) => c.id) : [],
|
|
43
|
-
options: entries.map((c) => ({
|
|
44
|
-
value: c.id,
|
|
45
|
-
title: c.title,
|
|
46
|
-
group: c.group,
|
|
47
|
-
detail: formatKeys(keys[c.id as CommandId], ui.plain),
|
|
48
|
-
...(kind === "palette" && c.cli ? { cli: c.cli } : {}),
|
|
49
|
-
})),
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
const run = (_p: unknown, id: string) => {
|
|
54
|
-
app.dispatch({ type: "close" });
|
|
55
|
-
keymap.dispatchCommand(id);
|
|
56
|
-
};
|
|
57
|
-
useDialogHandler("palette", run);
|
|
58
|
-
useDialogHandler("help", run);
|
|
59
|
-
return open;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** The storybook's picker (CATHERD_STORY=1): a story plays from a clean state. */
|
|
63
|
-
function useStories(on: boolean) {
|
|
64
|
-
const app = useApp();
|
|
65
|
-
const keymap = useKeymap();
|
|
66
|
-
useDialogHandler("stories", (_p, name) => {
|
|
67
|
-
app.dispatch({ type: "close" });
|
|
68
|
-
const story = STORIES.find((s) => s.name === name);
|
|
69
|
-
if (story) void playStory(story, app, keymap, () => new Promise((r) => app.clock.setTimeout(r, 50)));
|
|
70
|
-
});
|
|
71
|
-
return on
|
|
72
|
-
? () =>
|
|
73
|
-
app.dispatch({
|
|
74
|
-
type: "open",
|
|
75
|
-
dialog: {
|
|
76
|
-
kind: "select",
|
|
77
|
-
purpose: { type: "stories" },
|
|
78
|
-
title: "Stories",
|
|
79
|
-
empty: "no stories",
|
|
80
|
-
options: STORIES.map((s) => ({ value: s.name, title: s.title, detail: s.name })),
|
|
81
|
-
},
|
|
82
|
-
})
|
|
83
|
-
: null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function Screen(props: { story: boolean }) {
|
|
87
|
-
const app = useApp();
|
|
88
|
-
const data = useData();
|
|
89
|
-
const dims = useTerminalDimensions();
|
|
90
|
-
const openCommands = useCommandDialogs();
|
|
91
|
-
const openStories = useStories(props.story);
|
|
92
|
-
useProfileDialogs();
|
|
93
|
-
const { state } = app;
|
|
94
|
-
const dirty = totalDirty(state);
|
|
95
|
-
const quit = () => {
|
|
96
|
-
const dirty = totalDirty(app.getState());
|
|
97
|
-
if (dirty === 0) return app.exit(0);
|
|
98
|
-
app.dispatch({
|
|
99
|
-
type: "open",
|
|
100
|
-
dialog: {
|
|
101
|
-
kind: "confirm",
|
|
102
|
-
purpose: { type: "quit" },
|
|
103
|
-
title: "Quit with unsaved changes?",
|
|
104
|
-
message: [`${plural(dirty, "unsaved change")} will be lost.`],
|
|
105
|
-
yes: "Discard and quit",
|
|
106
|
-
no: "Keep editing",
|
|
107
|
-
destructive: true,
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
};
|
|
111
|
-
useDialogHandler("quit", () => app.exit(0));
|
|
112
|
-
const go = (by: number) => {
|
|
113
|
-
const tab = app.getState().tab;
|
|
114
|
-
app.dispatch({
|
|
115
|
-
type: "tab",
|
|
116
|
-
tab: TABS[(TABS.indexOf(tab) + by + TABS.length) % TABS.length] ?? "status",
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
useCommandLayer("global", {
|
|
120
|
-
// spec §9.2 ctrl+c: close a dialog, else clear a text input, else quit when clean; when dirty a second
|
|
121
|
-
// press within 1.5 s discards and exits 130
|
|
122
|
-
"app.interrupt": () => {
|
|
123
|
-
const state = app.getState();
|
|
124
|
-
const dirty = totalDirty(state);
|
|
125
|
-
// a save that is writing is never cut off: nothing closes or quits until it settles
|
|
126
|
-
if (isSaving(state)) return app.toast({ variant: "info", message: "saving… wait for it to finish" });
|
|
127
|
-
if (state.dialogs.length) return app.dispatch({ type: "close" });
|
|
128
|
-
if (app.back("input")) return;
|
|
129
|
-
if (dirty === 0) return app.exit(0);
|
|
130
|
-
const now = app.clock.now();
|
|
131
|
-
if (isArmed(state, "interrupt", "app", now)) return app.exit(130);
|
|
132
|
-
app.dispatch({ type: "arm", what: "interrupt", target: "app", at: now });
|
|
133
|
-
app.toast({
|
|
134
|
-
variant: "warning",
|
|
135
|
-
message: `${plural(dirty, "unsaved change")}: ctrl+c again to discard and quit`,
|
|
136
|
-
});
|
|
137
|
-
},
|
|
138
|
-
// esc backs out one level and never quits; an armed double press is a level of its own
|
|
139
|
-
"app.back": () => {
|
|
140
|
-
const state = app.getState();
|
|
141
|
-
const a = state.armed;
|
|
142
|
-
if (a) app.dispatch({ type: "disarm" });
|
|
143
|
-
if (a && isArmed(state, a.what, a.target, app.clock.now())) return;
|
|
144
|
-
if (state.dialogs.length) return app.dispatch({ type: "close" });
|
|
145
|
-
app.back();
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
useCommandLayer("app", {
|
|
149
|
-
"app.palette": () => openCommands("palette"),
|
|
150
|
-
"app.help": () => openCommands("help"),
|
|
151
|
-
"app.quit": quit,
|
|
152
|
-
"tab.status": () => app.dispatch({ type: "tab", tab: "status" }),
|
|
153
|
-
"tab.profiles": () => app.dispatch({ type: "tab", tab: "profiles" }),
|
|
154
|
-
"tab.runs": () => app.dispatch({ type: "tab", tab: "runs" }),
|
|
155
|
-
"tab.next": () => go(1),
|
|
156
|
-
"tab.prev": () => go(-1),
|
|
157
|
-
"profile.new": () => openNewProfile(app),
|
|
158
|
-
"profile.list": () => openProfileList(app, data),
|
|
159
|
-
...(openStories ? { "app.stories": openStories } : {}),
|
|
160
|
-
});
|
|
161
|
-
const checking = useDelayedPresence(data.checking);
|
|
162
|
-
const live = (data.runs.value?.rows ?? []).some((r) => r.live > 0);
|
|
163
|
-
const mood: Mood = data.report && !data.report.ready ? "failed" : live ? "working" : "good";
|
|
164
|
-
const draft = currentDraft(state);
|
|
165
|
-
const shown = draft?.name ?? data.profiles.value?.here ?? null;
|
|
166
|
-
const height = Math.max(1, dims.height - CHROME_ROWS);
|
|
167
|
-
const w = dims.width;
|
|
168
|
-
return (
|
|
169
|
-
<box flexDirection="column" width={w} height={dims.height}>
|
|
170
|
-
<box flexDirection="row" width={w} height={1}>
|
|
171
|
-
<Header
|
|
172
|
-
version={app.effects.version}
|
|
173
|
-
profile={shown}
|
|
174
|
-
active={shown !== null && shown === data.profiles.value?.here}
|
|
175
|
-
label={hereWord(data.profiles.value ?? { repo: null })}
|
|
176
|
-
dirty={dirty}
|
|
177
|
-
mood={mood}
|
|
178
|
-
width={checking ? w - 18 : w}
|
|
179
|
-
/>
|
|
180
|
-
{checking ? (
|
|
181
|
-
<box width={18}>
|
|
182
|
-
<Spinner label="checking setup" />
|
|
183
|
-
</box>
|
|
184
|
-
) : null}
|
|
185
|
-
</box>
|
|
186
|
-
<Tabs tab={state.tab} width={w} />
|
|
187
|
-
<Rule width={w} />
|
|
188
|
-
<Behind width={w} height={height}>
|
|
189
|
-
{state.tab === "status" ? <StatusView width={w} height={height} /> : null}
|
|
190
|
-
{state.tab === "profiles" ? <ProfilesView width={w} height={height} /> : null}
|
|
191
|
-
{state.tab === "runs" ? <RunsView width={w} height={height} /> : null}
|
|
192
|
-
</Behind>
|
|
193
|
-
<Rule width={w} />
|
|
194
|
-
<Footer width={w} />
|
|
195
|
-
<DialogHost />
|
|
196
|
-
<ToastHost />
|
|
197
|
-
</box>
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Spec §9: one app, three tabs, dialogs and toasts over them. Must sit inside `Providers`. */
|
|
202
|
-
export function App(props: { story?: boolean }) {
|
|
203
|
-
return (
|
|
204
|
-
<DataProvider>
|
|
205
|
-
<Screen story={props.story === true} />
|
|
206
|
-
</DataProvider>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import { useApp } from "../providers/app.tsx";
|
|
3
|
-
import { useUi } from "../providers/theme.tsx";
|
|
4
|
-
import { DialogConfirm } from "../widgets/dialog-confirm.tsx";
|
|
5
|
-
import { DialogPrompt } from "../widgets/dialog-prompt.tsx";
|
|
6
|
-
import { DialogSelect } from "../widgets/dialog-select.tsx";
|
|
7
|
-
import { SaveDialog } from "./save-dialog.tsx";
|
|
8
|
-
|
|
9
|
-
/** Draws the top dialog of the stack; a new dialog remounts, so its cursor and filter start fresh. */
|
|
10
|
-
export function DialogHost() {
|
|
11
|
-
const { state } = useApp();
|
|
12
|
-
const top = state.dialogs.at(-1);
|
|
13
|
-
if (!top) return null;
|
|
14
|
-
const key = `${state.dialogs.length}:${top.purpose.type}`;
|
|
15
|
-
switch (top.kind) {
|
|
16
|
-
case "select":
|
|
17
|
-
return <DialogSelect key={key} dialog={top} />;
|
|
18
|
-
case "confirm":
|
|
19
|
-
return <DialogConfirm key={key} dialog={top} />;
|
|
20
|
-
case "prompt":
|
|
21
|
-
return <DialogPrompt key={key} dialog={top} />;
|
|
22
|
-
case "save":
|
|
23
|
-
return <SaveDialog key={key} dialog={top} />;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* What an open dialog is drawn over. Without colour nothing can paint the panel opaque (a space
|
|
29
|
-
* with no background shows what is beneath), so there the dialog replaces these lines instead.
|
|
30
|
-
*/
|
|
31
|
-
export function Behind(props: { width: number; height: number; children: ReactNode }) {
|
|
32
|
-
const { state } = useApp();
|
|
33
|
-
const ui = useUi();
|
|
34
|
-
return (
|
|
35
|
-
<box width={props.width} height={props.height}>
|
|
36
|
-
<box width={props.width} height={props.height} visible={!(state.dialogs.length > 0 && !ui.color)}>
|
|
37
|
-
{props.children}
|
|
38
|
-
</box>
|
|
39
|
-
</box>
|
|
40
|
-
);
|
|
41
|
-
}
|