carthagent 1.0.7 → 1.1.0-preview.13
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 +140 -83
- package/bin/ctg.mjs +14 -732
- package/docs/glyph-review/host-chrome-glyph-compatibility-v4.md +83 -0
- package/docs/glyph-review/test-isolation-v1.md +26 -0
- package/docs/glyph-review/typecheck-base.log +1 -0
- package/docs/glyph-review/typecheck-v4.log +1 -0
- package/docs/glyph-review/unicode-inventory-v4.csv +402 -0
- package/docs/help-dispatch-review.md +69 -0
- package/docs/legacy-cli.md +150 -0
- package/docs/live-theme-plugin-api.md +18 -0
- package/docs/native-cli.md +102 -0
- package/docs/native-compatibility.md +16 -0
- package/docs/native-daily-quests.md +132 -0
- package/docs/native-glyph-pty.md +59 -0
- package/docs/native-manual-testing.md +336 -0
- package/docs/native-slash-acceptance-handoff.md +49 -0
- package/docs/native-terminal-paired-failure-2026-10-17.md +40 -0
- package/docs/native-terminal-performance.md +310 -0
- package/docs/native-terminal-semantics-evidence/README.md +25 -0
- package/docs/native-terminal-semantics-evidence/controlled-host-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/current-acceptance-status.json +59 -0
- package/docs/native-terminal-semantics-evidence/final-binary-focused-85bb44a.json +154 -0
- package/docs/native-terminal-semantics-evidence/final-binary-full-incomplete-85bb44a.json +160 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-85bb44a.json +149 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-interactive-85bb44a.json +159 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-escape-unchanged-85bb44a.json +44 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-exact-identity-85bb44a.json +47 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-unchanged-model-incomplete-85bb44a.json +58 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/final-binary-picker-cancel-incomplete-85bb44a.json +62 -0
- package/docs/native-terminal-semantics-evidence/focused-clipboard-title-stale-binary.json +161 -0
- package/docs/native-terminal-semantics-evidence/layout-stale-binary-observation-not-accepted.json +153 -0
- package/docs/native-terminal-semantics-evidence/picker-latest-incomplete-attempt.json +62 -0
- package/docs/native-terminal-semantics-evidence/picker-previous-probe-pass.json +61 -0
- package/docs/native-terminal-semantics.md +89 -0
- package/docs/native-upgrade.md +177 -0
- package/docs/opencode-composed-source-review.md +89 -0
- package/docs/opencode-migration.md +225 -9
- package/docs/opencode-owned-runtime.md +241 -0
- package/docs/opencode-runtime.md +12 -0
- package/docs/opencode-theme-provider-proof/README.md +55 -0
- package/docs/opencode-theme-provider-proof/live-theme-api-base.patch +122 -0
- package/docs/opencode-theme-provider-proof/plugin-theme-provider.patch +107 -0
- package/docs/opencode-theme-provider-proof/session-ui-base.patch +1217 -0
- package/docs/owned-host-controls-proof.md +39 -0
- package/docs/owned-runtime-release-manifest.md +42 -0
- package/docs/owned-visual-clear-proof.md +40 -0
- package/docs/patches/opencode-v2.0.32-glyph-spinner-test-isolation-v1.patch +50 -0
- package/docs/patches/opencode-v2.0.32-glyphs.patch +3670 -0
- package/docs/patches/opencode-v2.0.32-help-dispatch.patch +156 -0
- package/docs/patches/opencode-v2.0.32-live-theme-api.patch +122 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v1.patch +5272 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v2.patch +6052 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v3.patch +8657 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v4.patch +9928 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch +13965 -0
- package/docs/patches/opencode-v2.0.32-session-ui.patch +5137 -0
- package/docs/release-notes-native-draft.md +73 -0
- package/docs/release-notes-preview.10.md +121 -0
- package/docs/release-notes-preview.11.md +76 -0
- package/docs/release-notes-preview.12.md +55 -0
- package/docs/release-notes-preview.13.md +68 -0
- package/docs/release-notes-preview.9.md +130 -0
- package/docs/source-preview.md +120 -0
- package/evaluate.mjs +28 -1
- package/lib/budget.mjs +1 -1
- package/lib/codegraph.mjs +1 -1
- package/lib/delivery-review.mjs +11 -1
- package/lib/delivery.mjs +3 -2
- package/lib/environment.mjs +8 -0
- package/lib/guidance.mjs +1 -1
- package/lib/opencode/cli-runtime.mjs +1171 -0
- package/lib/opencode/cli.mjs +252 -0
- package/lib/opencode/credentials.mjs +63 -8
- package/lib/opencode/demo.mjs +53 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +3 -0
- package/lib/opencode/evaluation.mjs +32 -12
- package/lib/opencode/headless.mjs +8 -4
- package/lib/opencode/provider-policy.mjs +23 -0
- package/lib/opencode/review.mjs +37 -16
- package/lib/opencode/runtime-artifact.mjs +335 -0
- package/lib/opencode/runtime-github-transport.mjs +121 -0
- package/lib/opencode/runtime-release.json +36 -0
- package/lib/opencode/runtime.mjs +299 -20
- package/lib/opencode/service.mjs +77 -35
- package/lib/opencode/session-options.mjs +6 -2
- package/lib/opencode/update.mjs +222 -0
- package/lib/opencode/workspace.mjs +1 -1
- package/lib/planning-advice.mjs +2 -2
- package/lib/providers/experiential.mjs +2 -2
- package/lib/tui/framing.mjs +1 -80
- package/lib/update.mjs +10 -1
- package/package.json +33 -7
- package/plugins/carthagent/README.md +23 -0
- package/plugins/carthagent/src/bootstrap.ts +1 -1
- package/plugins/carthagent/src/daily-evidence.mjs +57 -0
- package/plugins/carthagent/src/daily-quests-rpc.ts +50 -0
- package/plugins/carthagent/src/daily-source-rpc.ts +52 -0
- package/plugins/carthagent/src/index.ts +71 -24
- package/plugins/carthagent/src/rpc.ts +51 -6
- package/plugins/carthagent/src/server/codegraph.ts +12 -5
- package/plugins/carthagent/src/server/daily-quests-rpc.ts +277 -0
- package/plugins/carthagent/src/server/daily-source-rpc.ts +211 -0
- package/plugins/carthagent/src/server/delivery.ts +227 -112
- package/plugins/carthagent/src/server/framing.mjs +80 -0
- package/plugins/carthagent/src/server/policy.ts +1 -1
- package/plugins/carthagent/src/server/providers.ts +59 -31
- package/plugins/carthagent/src/server/session-controls.ts +3 -1
- package/plugins/carthagent/src/server/ship-agents.ts +77 -0
- package/plugins/carthagent/src/server/ship-context.mjs +60 -0
- package/plugins/carthagent/src/server/ship-guidance.ts +41 -0
- package/plugins/carthagent/src/server/ship-rpc.ts +69 -0
- package/plugins/carthagent/src/server/voyages-rpc.ts +223 -0
- package/plugins/carthagent/src/testing.ts +5 -0
- package/plugins/carthagent/src/tui.tsx +496 -0
- package/plugins/carthagent/src/ui/client.ts +125 -0
- package/plugins/carthagent/src/ui/commands.ts +70 -0
- package/plugins/carthagent/src/ui/daily-editor-focus.mjs +52 -0
- package/plugins/carthagent/src/ui/daily-editor.mjs +4 -0
- package/plugins/carthagent/src/ui/daily-quests-events.mjs +18 -0
- package/plugins/carthagent/src/ui/daily-quests-state.mjs +218 -0
- package/plugins/carthagent/src/ui/daily-quests.tsx +235 -0
- package/plugins/carthagent/src/ui/host-state.ts +42 -0
- package/plugins/carthagent/src/ui/model-command.ts +32 -0
- package/plugins/carthagent/src/ui/native-navigation.ts +23 -0
- package/plugins/carthagent/src/ui/native-review.d.mts +33 -0
- package/plugins/carthagent/src/ui/native-review.mjs +159 -0
- package/plugins/carthagent/src/ui/resume-session.mjs +34 -0
- package/plugins/carthagent/src/ui/select-mouse.ts +17 -0
- package/plugins/carthagent/src/ui/session-preferences.d.mts +7 -0
- package/plugins/carthagent/src/ui/session-preferences.mjs +14 -0
- package/plugins/carthagent/src/ui/theme-provision.d.mts +3 -0
- package/plugins/carthagent/src/ui/theme-provision.mjs +134 -0
- package/plugins/carthagent/src/ui/voyages-navigation-state.mjs +56 -0
- package/plugins/carthagent/src/ui/voyages-navigation.tsx +131 -0
- package/plugins/carthagent/src/ui/voyages-state.mjs +142 -0
- package/plugins/carthagent/src/ui/voyages.tsx +129 -0
- package/plugins/carthagent/src/ui/workspace-live.ts +61 -0
- package/plugins/carthagent/src/ui/workspace.tsx +379 -0
- package/plugins/carthagent/src/voyages-rpc.ts +60 -0
- package/plugins/carthagent/src/workspace-rpc.ts +36 -0
- package/plugins/carthagent/tests/commands.test.ts +107 -0
- package/plugins/carthagent/tests/session-preferences.test.mjs +41 -0
- package/plugins/carthagent/tests/voyages-navigation.test.tsx +269 -0
- package/plugins/carthagent/themes/carthage.json +67 -0
- package/plugins/carthagent/tui.ts +26 -1
- package/script/ctg/build-owned-runtime.sh +166 -0
- package/tests/budget-engine.test.mjs +4 -3
- package/tests/cli.test.mjs +52 -96
- package/tests/daily-evidence.test.mjs +62 -0
- package/tests/delivery-review.test.mjs +11 -0
- package/tests/delivery.test.mjs +22 -1
- package/tests/fixtures/native-discovery-plugin/server.ts +15 -0
- package/tests/generate-owned-runtime-release.test.mjs +354 -0
- package/tests/installed-runtime-contract.test.mjs +43 -0
- package/tests/native-cli-host-smoke.mjs +457 -0
- package/tests/native-daily-editor-focus.test.mjs +213 -0
- package/tests/native-daily-editor.test.mjs +43 -0
- package/tests/native-daily-quests-layout.test.mjs +53 -0
- package/tests/native-daily-quests-rpc.test.mjs +456 -0
- package/tests/native-daily-quests-ui.test.mjs +778 -0
- package/tests/native-daily-source.test.mjs +451 -0
- package/tests/native-demo-ui.test.mjs +48 -0
- package/tests/native-evaluation-admission.test.mjs +24 -0
- package/tests/native-fixture-browser-cleanup.test.mjs +65 -0
- package/tests/native-fixture-runtime.test.mjs +111 -0
- package/tests/native-fixture-stop-diagnostics.test.mjs +67 -0
- package/tests/native-glyph-pty.test.mjs +107 -0
- package/tests/native-manual-projects.test.mjs +170 -0
- package/tests/native-model-command.test.mjs +67 -0
- package/tests/native-owned-runtime-env.test.mjs +140 -0
- package/tests/native-paired-guards.test.mjs +464 -0
- package/tests/native-provider-policy.test.mjs +31 -0
- package/tests/native-pty-invocation.test.mjs +114 -0
- package/tests/native-pty-runtime.test.mjs +45 -0
- package/tests/native-resume-session.test.mjs +46 -0
- package/tests/native-review-consumer.test.mjs +286 -0
- package/tests/native-runtime-identity.test.mjs +214 -0
- package/tests/native-select-mouse.test.mjs +23 -0
- package/tests/native-ship-parity.test.mjs +526 -0
- package/tests/native-ship-plan.test.mjs +85 -0
- package/tests/native-ship-rpc.test.mjs +71 -0
- package/tests/native-ui-commands.test.mjs +6 -0
- package/tests/native-voyages-layout.test.mjs +28 -0
- package/tests/native-voyages-navigation.test.mjs +169 -0
- package/tests/native-voyages-rpc.test.mjs +395 -0
- package/tests/native-voyages-ui.test.mjs +277 -0
- package/tests/opencode-atomic-write.test.mjs +55 -0
- package/tests/opencode-cli.test.mjs +1312 -0
- package/tests/opencode-codegraph-plugin.test.mjs +47 -0
- package/tests/opencode-delivery.test.mjs +16 -0
- package/tests/opencode-providers.test.mjs +245 -18
- package/tests/opencode-runtime.test.mjs +546 -8
- package/tests/opencode-session-controls.test.mjs +15 -0
- package/tests/opencode-ship-rpc.test.mjs +71 -0
- package/tests/opencode-update.test.mjs +475 -0
- package/tests/opencode-workspace.test.mjs +51 -2
- package/tests/owned-runtime-publish-workflow.test.mjs +477 -0
- package/tests/release-workflow.test.mjs +46 -0
- package/tests/runtime-artifact.test.mjs +269 -0
- package/tests/runtime-development-trust.test.mjs +230 -0
- package/tests/runtime-github-transport.test.mjs +132 -0
- package/tests/runtime-token-env.test.mjs +23 -0
- package/tests/server.test.mjs +3 -1
- package/tests/ship-telemetry.test.mjs +1 -1
- package/tests/source-preview.test.mjs +253 -0
- package/tests/test_native_terminal_probe.py +76 -0
- package/tests/test_native_terminal_semantics.py +232 -0
- package/tests/update.test.mjs +42 -5
- package/tests/verify-draft-runtime-gate.test.mjs +280 -0
- package/tests/verify-owned-runtime-release.test.mjs +371 -0
- package/tests/workspace-live.test.mjs +127 -0
- package/tools/benchmark/diagnose-request.mjs +59 -36
- package/tools/benchmark/eval-common.mjs +3 -3
- package/tools/benchmark/evaluators.test.mjs +4 -4
- package/tools/benchmark/guard.test.mjs +35 -2
- package/tools/benchmark/guard.ts +20 -7
- package/tools/benchmark/planning-startup.test.mjs +30 -34
- package/tools/benchmark/runner.mjs +12 -7
- package/tools/benchmark/runner.test.mjs +23 -7
- package/tools/benchmark/upstream-error.test.mjs +16 -2
- package/tools/generate-owned-runtime-release.mjs +217 -0
- package/tools/installed-runtime-contract.mjs +16 -0
- package/tools/manual-runtime-manifest.mjs +39 -0
- package/tools/native-fixture-browser-cleanup.mjs +59 -0
- package/tools/native-fixture-stop-diagnostics.mjs +43 -0
- package/tools/native-fixture.mjs +199 -40
- package/tools/native-glyph-helpers.py +127 -0
- package/tools/native-glyph-pty.py +144 -0
- package/tools/native-manual-projects.mjs +259 -0
- package/tools/native-manual.mjs +338 -0
- package/tools/native-model-picker-acceptance.py +306 -0
- package/tools/native-owned-runtime-env.sh +47 -0
- package/tools/native-paired-guards.mjs +282 -0
- package/tools/native-paired-manifest.mjs +72 -0
- package/tools/native-paired-orchestration.sh +51 -0
- package/tools/native-plugin-readiness.mjs +33 -0
- package/tools/native-plugin-readiness.test.mjs +53 -0
- package/tools/native-pty-invocation.mjs +26 -0
- package/tools/native-pty-runtime.mjs +83 -0
- package/tools/native-pty-smoke.py +147 -24
- package/tools/native-public-repo-shell-result.mjs +13 -0
- package/tools/native-public-repo-shell-result.test.mjs +28 -0
- package/tools/native-runtime-identity.mjs +201 -0
- package/tools/native-slash-acceptance-env.py +38 -0
- package/tools/native-terminal-semantics.py +876 -0
- package/tools/owned-shell-observer/index.ts +16 -0
- package/tools/owned-shell-observer/package.json +1 -0
- package/tools/owned-visual-clear-env.py +48 -0
- package/tools/owned-visual-clear-model.py +21 -0
- package/tools/pinned-owned-runtime-inputs.mjs +46 -0
- package/tools/prepare-native-manual.mjs +122 -0
- package/tools/run-native-terminal-paired.sh +403 -0
- package/tools/runtime-token-env.mjs +8 -0
- package/tools/seed-native-switch-fixture.mjs +79 -0
- package/tools/test-native-controls.mjs +3 -1
- package/tools/test-native-daily-quests.mjs +65 -0
- package/tools/test-native-default-cli.mjs +106 -0
- package/tools/test-native-delivery-host.mjs +102 -20
- package/tools/test-native-evaluation.mjs +7 -3
- package/tools/test-native-glyph-pty.mjs +94 -0
- package/tools/test-native-host.mjs +87 -63
- package/tools/test-native-outcomes.mjs +3 -1
- package/tools/test-native-package.mjs +206 -38
- package/tools/test-native-performance.mjs +26 -0
- package/tools/test-native-product.mjs +90 -0
- package/tools/test-native-pty.mjs +34 -9
- package/tools/test-native-public-repo.mjs +224 -0
- package/tools/test-native-refresh-reentry.mjs +106 -0
- package/tools/test-native-report.mjs +27 -20
- package/tools/test-native-review.mjs +75 -0
- package/tools/test-native-runtime.mjs +120 -0
- package/tools/test-native-ship-models.mjs +176 -0
- package/tools/test-native-ship-walkthrough.mjs +96 -0
- package/tools/test-native-slash-commands.mjs +125 -0
- package/tools/test-native-slash-commands.py +242 -0
- package/tools/test-native-swe-host.mjs +224 -55
- package/tools/test-native-tasks.mjs +77 -0
- package/tools/test-native-terminal-performance.py +887 -0
- package/tools/test-native-terminal-readiness.sh +85 -0
- package/tools/test-native-terminal-semantics-installed.mjs +38 -0
- package/tools/test-native-visual-clear-installed.mjs +48 -0
- package/tools/test-owned-host-controls.mjs +238 -0
- package/tools/test-owned-opencode-runtime.mjs +35 -0
- package/tools/test-owned-visual-clear-env.py +120 -0
- package/tools/test-owned-visual-clear.py +373 -0
- package/tools/verify-draft-runtime-gate.mjs +138 -0
- package/tools/verify-owned-runtime-release.mjs +114 -0
- package/tools/verify-release-version.mjs +21 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { createRoot, createSignal, createEffect, onCleanup } from 'solid-js/dist/solid.js';
|
|
6
|
+
import { createDailyEditorFocus } from '../plugins/carthagent/src/ui/daily-editor-focus.mjs';
|
|
7
|
+
import { createDailyQuestsState } from '../plugins/carthagent/src/ui/daily-quests-state.mjs';
|
|
8
|
+
|
|
9
|
+
// Focus-only adapter, not a native renderer test: retain the pinned ordering
|
|
10
|
+
// (blur previous, install handlers, emit focused) and route keys to the owner.
|
|
11
|
+
function fixture(t, { microtasks = [], defer = callback => microtasks.push(callback) } = {}) {
|
|
12
|
+
let current;
|
|
13
|
+
class Input extends EventEmitter {
|
|
14
|
+
focused = false;
|
|
15
|
+
isDestroyed = false;
|
|
16
|
+
focusCalls = 0;
|
|
17
|
+
text = '';
|
|
18
|
+
focus() {
|
|
19
|
+
if (this.isDestroyed || this.focused) return;
|
|
20
|
+
current?.blur();
|
|
21
|
+
current = this;
|
|
22
|
+
this.focused = true;
|
|
23
|
+
this.focusCalls++;
|
|
24
|
+
this.emit('focused');
|
|
25
|
+
}
|
|
26
|
+
blur() { this.focused = false; if (current === this) current = undefined; }
|
|
27
|
+
destroy() { this.blur(); this.isDestroyed = true; }
|
|
28
|
+
}
|
|
29
|
+
const actions = new Input();
|
|
30
|
+
let input = new Input();
|
|
31
|
+
let state, update, focus, dispose;
|
|
32
|
+
createRoot(stop => {
|
|
33
|
+
dispose = stop;
|
|
34
|
+
[state, update] = createSignal({ phase: 'overview', hasDraft: false, busy: false, editorFocus: true, mode: 'base' });
|
|
35
|
+
focus = createDailyEditorFocus({ state, editor: () => input, defer });
|
|
36
|
+
focus.attachActions(actions);
|
|
37
|
+
createEffect(focus.reconcile);
|
|
38
|
+
onCleanup(focus.dispose);
|
|
39
|
+
});
|
|
40
|
+
t.after(dispose);
|
|
41
|
+
const flush = () => {
|
|
42
|
+
let count = 0;
|
|
43
|
+
while (microtasks.length) {
|
|
44
|
+
assert.ok(++count < 30, 'focus reconciliation must settle, not poll');
|
|
45
|
+
microtasks.shift()();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
actions, focus, dispose, flush, microtasks,
|
|
50
|
+
input: () => input,
|
|
51
|
+
mountEditor: () => { input = new Input(); return input; },
|
|
52
|
+
newInput: () => new Input(),
|
|
53
|
+
current: () => current,
|
|
54
|
+
patch: next => update(previous => ({ ...previous, ...next })),
|
|
55
|
+
key(text) { if (current === input) input.text += text; },
|
|
56
|
+
enterAction(run) { if (current === actions && focus.actionsEnabled()) run(); },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test('compose promise mounts editor before native 1ms refocus; focus event repairs ownership without Tab', async t => {
|
|
61
|
+
const f = fixture(t);
|
|
62
|
+
f.flush();
|
|
63
|
+
assert.equal(f.current(), f.actions);
|
|
64
|
+
const id = '11111111-1111-4111-8111-111111111111';
|
|
65
|
+
const detail = { id, status: 'open' };
|
|
66
|
+
const controller = createDailyQuestsState({
|
|
67
|
+
api: {
|
|
68
|
+
snapshot: async () => ({ accountKey: 'a'.repeat(64), slate: { items: [detail] }, records: [] }),
|
|
69
|
+
detail: async () => detail,
|
|
70
|
+
submit: () => assert.fail('focus must not send evidence'),
|
|
71
|
+
},
|
|
72
|
+
changed: view => f.patch({ phase: view.phase, hasDraft: !!view.draft, busy: view.busy }),
|
|
73
|
+
});
|
|
74
|
+
t.after(() => controller.dispose());
|
|
75
|
+
await controller.refresh(); await controller.select(id);
|
|
76
|
+
let resolve;
|
|
77
|
+
const selected = new Promise(done => { resolve = done; });
|
|
78
|
+
const savedFocus = f.actions;
|
|
79
|
+
const compose = controller.compose(() => selected);
|
|
80
|
+
f.patch({ mode: 'modal' });
|
|
81
|
+
f.flush();
|
|
82
|
+
resolve({ id, evidence: { goal: 'manual fixture' } });
|
|
83
|
+
f.patch({ mode: 'base' });
|
|
84
|
+
// clear() schedules the old renderable's focus after promise continuations.
|
|
85
|
+
const nativeTimer = () => savedFocus.focus();
|
|
86
|
+
await compose;
|
|
87
|
+
f.flush();
|
|
88
|
+
assert.equal(f.current(), f.input());
|
|
89
|
+
nativeTimer();
|
|
90
|
+
assert.equal(f.current(), f.actions, 'reproduce stale actual focus despite editor ownership');
|
|
91
|
+
let navigations = 0;
|
|
92
|
+
f.enterAction(() => navigations++);
|
|
93
|
+
assert.equal(navigations, 0, 'stale Enter cannot activate Back/refresh/submit');
|
|
94
|
+
f.flush();
|
|
95
|
+
f.key('Z\n');
|
|
96
|
+
assert.equal(f.input().text, 'Z\n');
|
|
97
|
+
assert.equal(f.current(), f.input());
|
|
98
|
+
assert.equal(f.actions.focused, false);
|
|
99
|
+
assert.equal(f.actions.listenerCount('focused'), 1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('default microtask runs after focus installation and rechecks latest ownership', async t => {
|
|
103
|
+
const f = fixture(t, { defer: queueMicrotask });
|
|
104
|
+
await Promise.resolve();
|
|
105
|
+
f.patch({ phase: 'edit', hasDraft: true });
|
|
106
|
+
await Promise.resolve();
|
|
107
|
+
f.actions.focus();
|
|
108
|
+
await Promise.resolve();
|
|
109
|
+
assert.equal(f.current(), f.input());
|
|
110
|
+
f.actions.focus();
|
|
111
|
+
f.patch({ mode: 'modal' });
|
|
112
|
+
await Promise.resolve();
|
|
113
|
+
assert.equal(f.current(), undefined, 'never reclaim focus from a modal');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Tab hands ownership to visible actions and back; stale event cannot reverse it', t => {
|
|
117
|
+
const f = fixture(t);
|
|
118
|
+
f.patch({ phase: 'edit', hasDraft: true }); f.flush();
|
|
119
|
+
f.actions.focus(); // reconciliation queued for editor
|
|
120
|
+
f.patch({ editorFocus: false }); f.flush();
|
|
121
|
+
assert.equal(f.current(), f.actions);
|
|
122
|
+
let selections = 0;
|
|
123
|
+
f.enterAction(() => selections++);
|
|
124
|
+
assert.equal(selections, 1);
|
|
125
|
+
f.patch({ editorFocus: true }); f.flush();
|
|
126
|
+
assert.equal(f.current(), f.input());
|
|
127
|
+
f.key('after Tab');
|
|
128
|
+
assert.equal(f.input().text, 'after Tab');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
for (const blocked of [{ mode: 'modal' }, { mode: 'palette' }, { busy: true }]) {
|
|
132
|
+
test(`pending repair respects ${JSON.stringify(blocked)} and restores only after unblock`, t => {
|
|
133
|
+
const f = fixture(t);
|
|
134
|
+
f.patch({ phase: 'edit', hasDraft: true }); f.flush();
|
|
135
|
+
f.actions.focus();
|
|
136
|
+
f.patch(blocked);
|
|
137
|
+
const modal = f.newInput();
|
|
138
|
+
modal.focus();
|
|
139
|
+
f.flush();
|
|
140
|
+
assert.equal(f.current(), modal, 'do not steal focus from an unrelated modal/control');
|
|
141
|
+
assert.equal(f.focus.editorEnabled(), false);
|
|
142
|
+
assert.equal(f.focus.actionsEnabled(), false);
|
|
143
|
+
f.patch({ mode: 'base', busy: false }); f.flush();
|
|
144
|
+
assert.equal(f.current(), f.input());
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
test('preview/edit remount uses current editor; cancel/invalidate cannot revive destroyed draft', t => {
|
|
149
|
+
const f = fixture(t);
|
|
150
|
+
f.patch({ phase: 'edit', hasDraft: true }); f.flush();
|
|
151
|
+
const oldEditor = f.input();
|
|
152
|
+
f.actions.focus();
|
|
153
|
+
oldEditor.destroy();
|
|
154
|
+
f.patch({ phase: 'preview' }); f.flush();
|
|
155
|
+
assert.equal(f.current(), f.actions);
|
|
156
|
+
const nextEditor = f.mountEditor();
|
|
157
|
+
f.patch({ phase: 'edit', editorFocus: true }); f.flush();
|
|
158
|
+
assert.equal(f.current(), nextEditor);
|
|
159
|
+
f.actions.focus();
|
|
160
|
+
nextEditor.destroy();
|
|
161
|
+
f.patch({ phase: 'overview', hasDraft: false }); f.flush();
|
|
162
|
+
assert.equal(f.current(), f.actions);
|
|
163
|
+
assert.equal(oldEditor.focusCalls, 1);
|
|
164
|
+
assert.equal(nextEditor.focusCalls, 1);
|
|
165
|
+
f.patch({ phase: 'edit', hasDraft: false }); f.flush();
|
|
166
|
+
assert.equal(f.current(), undefined, 'no editor focus without a live draft');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('unmount cancels pending repairs, detaches listeners and cannot steal next page focus', t => {
|
|
170
|
+
const f = fixture(t);
|
|
171
|
+
f.patch({ phase: 'edit', hasDraft: true }); f.flush();
|
|
172
|
+
f.actions.focus();
|
|
173
|
+
assert.ok(f.microtasks.length);
|
|
174
|
+
f.dispose();
|
|
175
|
+
assert.equal(f.actions.listenerCount('focused'), 0);
|
|
176
|
+
f.input().blur(); f.actions.blur();
|
|
177
|
+
const nextPage = f.newInput();
|
|
178
|
+
nextPage.focus();
|
|
179
|
+
f.flush();
|
|
180
|
+
assert.equal(f.current(), nextPage);
|
|
181
|
+
f.focus.reconcile();
|
|
182
|
+
assert.equal(f.microtasks.length, 0);
|
|
183
|
+
assert.equal(f.focus.actionsEnabled(), false);
|
|
184
|
+
assert.equal(f.focus.editorEnabled(), false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('replacing or destroying native action refs does not retain a stale focus listener', t => {
|
|
188
|
+
const f = fixture(t);
|
|
189
|
+
f.flush();
|
|
190
|
+
const replacement = f.newInput();
|
|
191
|
+
f.focus.attachActions(replacement); f.flush();
|
|
192
|
+
assert.equal(f.actions.listenerCount('focused'), 0);
|
|
193
|
+
assert.equal(replacement.listenerCount('focused'), 1);
|
|
194
|
+
replacement.destroy();
|
|
195
|
+
f.focus.reconcile(); f.flush();
|
|
196
|
+
assert.equal(replacement.focusCalls, 1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('page wires focus lifecycle and selection guard; pinned renderer exposes the used focus event contract', async () => {
|
|
200
|
+
const page = await readFile(new URL('../plugins/carthagent/src/ui/daily-quests.tsx', import.meta.url), 'utf8');
|
|
201
|
+
assert.match(page, /createEffect\(\(\) => focus\.reconcile\(\)\)/);
|
|
202
|
+
assert.match(page, /mode: context\.keymap\.mode\.current\(\)/);
|
|
203
|
+
assert.match(page, /ref=\{\(node\) => \{ actions = node; focus\.attachActions\(node\); \}\}/);
|
|
204
|
+
assert.match(page, /if \(!value \|\| !focus\.actionsEnabled\(\)\) return/);
|
|
205
|
+
assert.match(page, /onCleanup\(\(\) => \{ focus\.dispose\(\)/);
|
|
206
|
+
const api = await readFile(new URL('../node_modules/@opentui/core/Renderable.d.ts', import.meta.url), 'utf8');
|
|
207
|
+
assert.match(api, /FOCUSED = "focused"/);
|
|
208
|
+
assert.match(api, /focus\(\): void/);
|
|
209
|
+
assert.match(api, /blur\(\): void/);
|
|
210
|
+
assert.match(api, /get isDestroyed\(\): boolean/);
|
|
211
|
+
const selectApi = await readFile(new URL('../node_modules/@opentui/core/renderables/Select.d.ts', import.meta.url), 'utf8');
|
|
212
|
+
assert.match(selectApi, /setSelectedIndex\(index: number\): void/);
|
|
213
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { syncDailyEditor } from '../plugins/carthagent/src/ui/daily-editor.mjs';
|
|
5
|
+
import { createDailyQuestsState } from '../plugins/carthagent/src/ui/daily-quests-state.mjs';
|
|
6
|
+
|
|
7
|
+
test('pinned renderer empty content-change events read current text before canonical preview', async () => {
|
|
8
|
+
// Guard the actual pinned implementation, not the misleading Solid callback
|
|
9
|
+
// type. This is an adapter contract test, not a native FFI rendering test.
|
|
10
|
+
const module = await readFile(new URL('../node_modules/@opentui/core/index.node.js', import.meta.url), 'utf8');
|
|
11
|
+
const chunkName = module.match(/from "(\.\/chunk-node-[^"]+)"/g)
|
|
12
|
+
.map(value => value.match(/"([^"]+)"/)[1]);
|
|
13
|
+
const chunks = await Promise.all(chunkName.map(name => readFile(new URL(`../node_modules/@opentui/core/${name.slice(2)}`, import.meta.url), 'utf8')));
|
|
14
|
+
assert.ok(chunks.some(source => source.includes('this._contentChangeListener({})')), 'Pinned renderer event shape changed; review the adapter');
|
|
15
|
+
const id = '11111111-1111-4111-8111-111111111111';
|
|
16
|
+
const detail = { id, status: 'open', definition: { title: 'Adapter contract' }, latestSubmission: null };
|
|
17
|
+
const writes = [];
|
|
18
|
+
let view;
|
|
19
|
+
const controller = createDailyQuestsState({ api: {
|
|
20
|
+
snapshot: async () => ({ accountKey: 'a'.repeat(64), slate: { items: [detail] }, records: [] }),
|
|
21
|
+
detail: async () => detail,
|
|
22
|
+
submit: async input => { writes.push(input); },
|
|
23
|
+
}, changed: next => { view = next; } });
|
|
24
|
+
try {
|
|
25
|
+
await controller.refresh(); await controller.select(id);
|
|
26
|
+
await controller.compose(async () => ({ id, evidence: { projectId: id, outcomeId: id, runId: id, revision: '', goal: 'private text selected originally', change: '', checks: [], findings: [] } }));
|
|
27
|
+
const edited = JSON.parse(view.draft.text);
|
|
28
|
+
edited.goal = 'Redacted user-approved goal'; edited.revision = 'a'.repeat(40);
|
|
29
|
+
const editor = { plainText: JSON.stringify(edited) };
|
|
30
|
+
const onContentChange = () => syncDailyEditor(editor, controller);
|
|
31
|
+
onContentChange({});
|
|
32
|
+
controller.preview();
|
|
33
|
+
assert.equal(view.phase, 'preview');
|
|
34
|
+
assert.equal(view.preview.evidence.goal, edited.goal);
|
|
35
|
+
assert.equal(view.preview.evidence.revision, edited.revision);
|
|
36
|
+
assert.doesNotMatch(JSON.stringify(view.preview), /private text/);
|
|
37
|
+
assert.deepEqual(writes, []);
|
|
38
|
+
controller.backToEdit();
|
|
39
|
+
edited.goal = ''; editor.plainText = JSON.stringify(edited);
|
|
40
|
+
onContentChange({}); controller.preview();
|
|
41
|
+
assert.equal(view.preview.evidence.goal, '');
|
|
42
|
+
} finally { controller.dispose(); }
|
|
43
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
const page = await readFile(new URL('../plugins/carthagent/src/ui/daily-quests.tsx', import.meta.url), 'utf8');
|
|
6
|
+
const voyages = await readFile(new URL('../plugins/carthagent/src/ui/voyages.tsx', import.meta.url), 'utf8');
|
|
7
|
+
const tui = await readFile(new URL('../plugins/carthagent/src/tui.tsx', import.meta.url), 'utf8');
|
|
8
|
+
const server = await readFile(new URL('../plugins/carthagent/src/index.ts', import.meta.url), 'utf8');
|
|
9
|
+
|
|
10
|
+
test('daily quests are registered in the native plugin and reachable from Voyages', () => {
|
|
11
|
+
assert.match(server, /registerDailyQuestsRpc\(context\)/);
|
|
12
|
+
assert.match(server, /registerDailySourceRpc\(context\)/);
|
|
13
|
+
assert.match(tui, /name: "daily-quests", render:.*DailyQuestsPage/);
|
|
14
|
+
assert.match(voyages, /name: "Daily coding quests"/);
|
|
15
|
+
assert.match(voyages, /name: "daily-quests"/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('evidence editing and the complete outbound preview remain native and scrollable', () => {
|
|
19
|
+
assert.match(page, /<scrollbox ref=\{summary\} flexBasis=\{0\} flexGrow=\{1\} minHeight=\{1\}/);
|
|
20
|
+
assert.match(page, /<textarea ref=\{editor\}[^>]*onContentChange=\{\(\) => syncDailyEditor\(editor, controller\)\}/);
|
|
21
|
+
assert.match(page, /bind: "tab"/);
|
|
22
|
+
assert.match(page, /JSON.stringify\(view\(\).preview, null, 2\)/);
|
|
23
|
+
assert.match(page, /JSON.stringify\(record\(\).request, null, 2\)/);
|
|
24
|
+
assert.match(page, /Ctrl\+P preview, never sends/);
|
|
25
|
+
for (const text of page.matchAll(/<text\b([^>]*)>/g)) assert.match(text[1], /flexShrink=\{0\}/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('daily cancellation helper is wired to the actual actions ref and page-scoped notice', () => {
|
|
29
|
+
assert.match(page, /actions = node; focus.attachActions\(node\)/);
|
|
30
|
+
assert.match(page, /handleDailyQuestsEvent\(\{\s*controller, event: details, location: location\(\), changedLocation, actions/);
|
|
31
|
+
assert.match(page, /interrupted: !!\(view\(\).draft \|\| view\(\).preview \|\| replayID\(\) \|\| activeDialog\)/);
|
|
32
|
+
assert.match(page, /notice: setCancellationNotice/);
|
|
33
|
+
assert.match(page, /<Show when=\{cancellationNotice\(\)\}>/);
|
|
34
|
+
assert.match(page, /stop\(\); actions = undefined; controller.dispose\(\)/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('selection starts empty and external consent, retention, uncertainty and no-reward limits are explicit', () => {
|
|
38
|
+
assert.match(page, /let goal = false, change = false/);
|
|
39
|
+
assert.match(page, /new Set<string>\(\), findings = new Set<string>\(\)/);
|
|
40
|
+
assert.match(page, /Consent and send/);
|
|
41
|
+
assert.match(page, /External JEV receives/);
|
|
42
|
+
assert.match(page, /Metadata: 90 days/);
|
|
43
|
+
assert.match(page, /not a background deletion timer/);
|
|
44
|
+
assert.match(page, /automatic secret detection is not provided/);
|
|
45
|
+
assert.match(page, /not independent verification/);
|
|
46
|
+
assert.match(page, /No coins, XP, distance, inventory or other rewards/);
|
|
47
|
+
assert.match(page, /Earlier failed-before execution is not recorded and is never inferred/);
|
|
48
|
+
assert.match(page, /c\.after === "passed" \? "fresh" : "not fresh"/);
|
|
49
|
+
assert.match(page, /\(\{ name: id, before, after, revision, result \}\)/);
|
|
50
|
+
assert.match(page, /view\(\)\.draft\?\.provenance/);
|
|
51
|
+
assert.match(page, /This does not cancel a server job, withdraw evidence already sent, or promise provider\/backup deletion/);
|
|
52
|
+
assert.doesNotMatch(page, /session\.export|file\.read|sendNativeReviewKickoff|outputTail/);
|
|
53
|
+
});
|