0d06a4c5fe
- Replace monolithic app.ts / app-conversation.ts / app-device-pod.ts /
app-skills.ts / app-helpers.ts / app-session-tabs.ts / auth.ts and the
364-line index.html with a Vite-built React + TypeScript SPA.
- New src/ module tree: main.tsx, App.tsx, components/{layout,auth,
conversation,command-bar,device-pod,skills,settings,help},
hooks/, services/api, services/auth, state, types, logic/ (pure
helpers from the old tree), styles/.
- Pure-logic modules (composer-policy, code-agent-facts, code-agent-
status, live-status, message-markdown, app-trace, app-session-tabs,
runtime) move to src/logic/ with their tests preserved; tests still
auto-discover.
- Build pipeline switches from Bun.build to Vite: vite.config.ts
inlines everything into a single app.js + app-assets/ for the dist;
scripts/dist-contract.ts now runs vite build and asserts the dist
against a fresh build instead of comparing dist to source.
- scripts/check.ts now verifies the React entry, the mount-only
index.html, Vite dist, the composer / session / device pod / skills
/ settings / help contracts, the React state shape, and the new
test file locations. It also refuses to run if any legacy app-*.ts
or auth.ts survives at the top level.
- New scripts/frontend-guard.ts enforces the 400-line migration target,
refuses the legacy el map and document.getElementById markers,
refuses to keep app-*.ts / auth.ts at the top level, and verifies
the required module directory layout.
- Update scripts/src/dev-cloud-workbench-smoke-lib.mjs to read the new
src/services/, src/logic/ paths and refresh cloudWebModuleSourceFiles.
- index.html is now a mount shell only (no login shell, no workbench
shell, no device pod sidebar markup). React renders the full UI from
src/main.tsx, satisfying the issue-756 migration goal of catching
unclosed tags at the TypeScript/JSX compile step instead of relying
on browser DOM healing.
- web:check (now: check + frontend-guard + tsc-check + bun test) is
green: 51 pass / 0 fail, Vite build produces dist/app.js (379 kB)
+ dist/index.html + dist/app-assets/. Vite build is reproducible;
dist freshness is verified against a fresh Vite build.
- Grandfathered pure-logic files (> 600 lines): src/logic/app-trace.ts
(1367), src/logic/live-status.ts (1006), src/logic/code-agent-facts.ts
(548), src/logic/code-agent-status.ts (572). Reason: each is a tested
pure-logic classifier with a public contract consumed by both the
CLI renderer and the Web renderer; splitting now would fork the
public function shape across two PRs and the migration target is the
React entry + module split, not a pure-logic refactor.
Refs #756. Closes #756 once merged + deployed to hwlab-v02.
Co-authored-by: Codex <codex@local>
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
|
|
import { computeCodeAgentComposerState } from "./composer-policy.ts";
|
|
|
|
test("composer stays unlocked and routes active turns to steer", () => {
|
|
const state = computeCodeAgentComposerState({
|
|
chatPending: true,
|
|
currentRequest: {
|
|
traceId: "trc_active",
|
|
conversationId: "cnv_active",
|
|
sessionId: "ses_active",
|
|
threadId: "thread-active"
|
|
},
|
|
messages: [{ role: "agent", status: "running", traceId: "trc_active" }]
|
|
});
|
|
|
|
expect(state.locked).toBe(false);
|
|
expect(state.disabled).toBe(false);
|
|
expect(state.submitMode).toBe("steer");
|
|
expect(state.route).toBe("/v1/agent/chat/steer");
|
|
expect(state.targetTraceId).toBe("trc_active");
|
|
expect(state.conversationId).toBe("cnv_active");
|
|
});
|
|
|
|
test("composer routes idle or terminal state to a normal turn", () => {
|
|
const state = computeCodeAgentComposerState({
|
|
sessionStatus: "completed",
|
|
workspace: {
|
|
workspaceId: "wsp_1",
|
|
revision: 3,
|
|
selectedConversationId: "cnv_1",
|
|
selectedAgentSessionId: "ses_1"
|
|
}
|
|
});
|
|
|
|
expect(state.locked).toBe(false);
|
|
expect(state.disabled).toBe(false);
|
|
expect(state.submitMode).toBe("turn");
|
|
expect(state.route).toBe("/v1/agent/chat");
|
|
expect(state.targetTraceId).toBeNull();
|
|
expect(state.workspaceId).toBe("wsp_1");
|
|
expect(state.workspaceRevision).toBe(3);
|
|
});
|
|
|
|
test("composer treats workspace activeTraceId as a steerable Web turn", () => {
|
|
const state = computeCodeAgentComposerState({
|
|
workspace: {
|
|
activeTraceId: "trc_shared_active",
|
|
selectedConversationId: "cnv_shared",
|
|
selectedAgentSessionId: "ses_shared",
|
|
threadId: "thread-shared"
|
|
}
|
|
});
|
|
|
|
expect(state.locked).toBe(false);
|
|
expect(state.disabled).toBe(false);
|
|
expect(state.submitMode).toBe("steer");
|
|
expect(state.targetTraceId).toBe("trc_shared_active");
|
|
expect(state.route).toBe("/v1/agent/chat/steer");
|
|
});
|
|
|
|
test("composer does not steer from stale terminal activeTraceId", () => {
|
|
const state = computeCodeAgentComposerState({
|
|
sessionStatus: "completed",
|
|
workspace: {
|
|
activeTraceId: "trc_stale_active",
|
|
sessionStatus: "completed",
|
|
selectedConversationId: "cnv_stale"
|
|
}
|
|
});
|
|
|
|
expect(state.locked).toBe(false);
|
|
expect(state.disabled).toBe(true);
|
|
expect(state.disabledReason).toBe("session_required");
|
|
expect(state.submitMode).toBe("turn");
|
|
expect(state.targetTraceId).toBeNull();
|
|
expect(state.route).toBe("/v1/agent/chat");
|
|
});
|
|
|
|
test("composer requires a new manual session after failed session", () => {
|
|
const state = computeCodeAgentComposerState({
|
|
sessionStatus: "failed",
|
|
workspace: {
|
|
selectedConversationId: "cnv_failed",
|
|
selectedAgentSessionId: "ses_failed"
|
|
}
|
|
});
|
|
|
|
expect(state.submitMode).toBe("turn");
|
|
expect(state.disabled).toBe(false);
|
|
expect(state.disabledReason).toBeNull();
|
|
expect(state.sessionUsable).toBe(true);
|
|
});
|