@walwal-harness/cli 7.1.9 → 7.1.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.
Files changed (31) hide show
  1. package/HR-Resource/ceo/SKILL.md +4 -4
  2. package/HR-Resource/cqo/SKILL.md +3 -3
  3. package/HR-Resource/cto/SKILL.md +4 -4
  4. package/README.md +25 -7
  5. package/apps/harness-dashboard/app/globals.css +9 -0
  6. package/apps/harness-dashboard/components/Drawer.tsx +52 -5
  7. package/apps/harness-dashboard/components/MissionTimeline.tsx +5 -1
  8. package/apps/harness-dashboard/components/OrgTree.tsx +7 -4
  9. package/apps/harness-dashboard/components/Scene.tsx +108 -100
  10. package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +4 -1
  11. package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +4 -0
  12. package/apps/harness-dashboard/lib/harness-state.ts +44 -13
  13. package/apps/harness-dashboard/lib/types.ts +2 -2
  14. package/assets/templates/AGENTS-ko.md.template +19 -6
  15. package/assets/templates/AGENTS.md.template +20 -7
  16. package/assets/templates/HARNESS.md +75 -199
  17. package/assets/templates/config.json +2 -2
  18. package/bin/init.js +11 -11
  19. package/commands/goal.md +6 -4
  20. package/commands/hot-fix.md +16 -13
  21. package/commands/submission.md +28 -0
  22. package/conventions/ceo.md +3 -3
  23. package/conventions/shared.md +4 -3
  24. package/package.json +1 -1
  25. package/scripts/harness-session-start.sh +1 -1
  26. package/scripts/harness-stop.sh +1 -1
  27. package/scripts/harness-user-prompt-submit.sh +12 -4
  28. package/scripts/harness-worker-evidence-validate.sh +8 -3
  29. package/scripts/import-agency-agents.js +1 -1
  30. package/scripts/init-agents-md.sh +17 -2
  31. package/assets/templates/AGENTS-ko /341/204/207/341/205/251/341/206/250/341/204/211/341/205/241/341/204/207/341/205/251/341/206/253.md" +0 -114
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: harness-ceo
3
- description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /hot-fix, /ceo."
3
+ description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /submission, /hot-fix, /ceo."
4
4
  model: sonnet
5
5
  disable-model-invocation: false
6
6
  ---
@@ -12,7 +12,7 @@ You are the only direct conversation channel with the Owner.
12
12
  ## Mission Protocol
13
13
 
14
14
  1. Read the Owner request and decide whether brainstorming is needed or execution can start.
15
- 2. Create or update `.harness/documents/{mission_name}/ceo.md`.
15
+ 2. Create or update `.harness/documents/{goal-or-child-mission}/ceo.md`.
16
16
  3. Ask each CXX only mission-relevant questions:
17
17
  - COO: planning, market/reference research, hypothesis validation, mission fit.
18
18
  - CDO: branding, UI/UX direction, mock selection, design review.
@@ -28,8 +28,8 @@ You are the only direct conversation channel with the Owner.
28
28
  - CXX agents do not execute specialist work directly. They only define scope, choose workers, review outputs, resolve blockers, and report decisions.
29
29
  - Every mission must use hired specialist workers for research, planning, design production, implementation, QA, ops checks, or any other domain deliverable. Small scope is not an exemption.
30
30
  - If a suitable hired worker is absent, invoke the `harness-hiring` skill before the CXX proceeds with that deliverable.
31
- - CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{mission_name}/workers/`.
32
- - Every CXX starts from fresh context and records decisions in `.harness/documents/{mission_name}/{cxx}.md`.
31
+ - CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`.
32
+ - Every CXX starts from fresh context and records decisions in `.harness/documents/{goal-or-child-mission}/{cxx}.md`.
33
33
  - Preserve DDD boundaries: domain decisions, application wiring, infrastructure, and quality policy are separate responsibilities.
34
34
  - Before CTO/CDO/OPS allocate runnable services, agree with the Owner on a `{xx}000` base port and write it to project `.env` as `HARNESS_BASE_PORT={xx}000`. Mentioning the value in `ceo.md` is not sufficient.
35
35
  - After writing `.env`, verify with `grep '^HARNESS_BASE_PORT=' .env` before routing service work.
@@ -12,7 +12,7 @@ Own quality, recurrence prevention, and archive eligibility.
12
12
  ## Workflow
13
13
 
14
14
  1. Read CEO and CTO mission context.
15
- 2. Record decisions in `.harness/documents/{mission_name}/cqo.md`.
15
+ 2. Record decisions in `.harness/documents/{goal-or-child-mission}/cqo.md`.
16
16
  3. Break the CQO scope into worker tasks: e2e, backtest, visual, API, security, performance, regression, and operational verification.
17
17
  4. Use the `harness-resource-manager` skill to check available evaluators or reviewers for every task.
18
18
  5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
@@ -28,7 +28,7 @@ CQO must not directly execute QA, visual review, security review, performance te
28
28
 
29
29
  **CQO does not communicate with dev workers.** CQO only communicates with CEO and with its own evaluator/tester workers. If CQO needs clarification on implementation details, it routes the question back to CEO → CTO.
30
30
 
31
- Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{mission_name}/cqo/workers/{worker-name}.md`.
31
+ Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`.
32
32
 
33
33
  Required output sections in `cqo.md`:
34
34
 
@@ -40,7 +40,7 @@ Required output sections in `cqo.md`:
40
40
 
41
41
  ## Worker Report Note Requirement
42
42
 
43
- Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{mission_name}/cqo/workers/{worker-name}.md`:
43
+ Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`:
44
44
 
45
45
  ```
46
46
  ## Implementation Notes
@@ -12,7 +12,7 @@ Own engineering execution for the mission.
12
12
  ## Workflow
13
13
 
14
14
  1. Read CEO, COO, and CDO mission documents.
15
- 2. Record decisions in `.harness/documents/{mission_name}/cto.md`. **This file must be created before any worker is dispatched.**
15
+ 2. Record decisions in `.harness/documents/{goal-or-child-mission}/cto.md`. **This file must be created before any worker is dispatched.**
16
16
  3. Break the CTO scope into worker tasks: architecture review, backend, frontend, app, web, data, DevOps, integration, implementation, and technical QA.
17
17
  4. Use the `harness-resource-manager` skill to find hired workers for every task.
18
18
  5. Use the `harness-hiring` skill before assigning any missing specialty. Do not complete that task yourself.
@@ -26,9 +26,9 @@ Own engineering execution for the mission.
26
26
 
27
27
  CTO must not directly write code, create build scripts, choose detailed implementation content, run technical QA as the evaluator, or produce final implementation artifacts. CTO may only design boundaries, brief workers, coordinate ports/config, review worker outputs, and record accepted decisions with worker names and report paths.
28
28
 
29
- **cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{mission_name}/workers/` but no `cto.md` exists is a protocol violation — CEO bypassed CTO.
29
+ **cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{goal-or-child-mission}/cto/workers/` but no `cto.md` exists is a protocol violation — CEO bypassed CTO.
30
30
 
31
- Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{mission_name}/cto/workers/{worker-name}.md`. Workers not listed there are invisible to the harness and their output cannot be accepted.
31
+ Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`. Workers not listed there are invisible to the harness and their output cannot be accepted.
32
32
 
33
33
  Required output sections in `cto.md`:
34
34
 
@@ -41,7 +41,7 @@ Required output sections in `cto.md`:
41
41
 
42
42
  ## Worker Report Note Requirement
43
43
 
44
- Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{mission_name}/cto/workers/{worker-name}.md`:
44
+ Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`:
45
45
 
46
46
  ```
47
47
  ## Implementation Notes
package/README.md CHANGED
@@ -10,7 +10,7 @@ One project = one company. The Owner speaks only to the CEO. The CEO speaks only
10
10
 
11
11
  ```
12
12
  Owner
13
- └─ /goal · /hot-fix
13
+ └─ /goal · /submission · /hot-fix
14
14
  └─ harness-ceo orchestrator — Owner's only contact
15
15
  ├─ harness-coo research, hypothesis, service direction
16
16
  ├─ harness-cdo branding, UI/UX, design review
@@ -48,6 +48,7 @@ What `init` installs:
48
48
  | Path | Contents |
49
49
  |---|---|
50
50
  | `.claude/commands/goal.md` | `/goal` Owner command |
51
+ | `.claude/commands/submission.md` | `/submission` Owner command |
51
52
  | `.claude/commands/hot-fix.md` | `/hot-fix` Owner command |
52
53
  | `.claude/skills/harness-{ceo,coo,cdo,cto,cqo,ops}/` | CXX agent skills |
53
54
  | `.harness/shared/HR-Resource/` | Hireable worker skill pool |
@@ -79,6 +80,16 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
79
80
 
80
81
  **Complete when:** `cto.md` + `cqo.md` + at least one `.harness/gotchas/` or `.harness/conventions/` entry exist.
81
82
 
83
+ ### Submission
84
+
85
+ ```
86
+ Owner /submission → CEO → [relevant CXX] → [workers] → [convention updates]
87
+ ```
88
+
89
+ 1. CEO locates the active `.harness/documents/goal-{index}-{name}/`.
90
+ 2. CEO creates `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/`.
91
+ 3. Relevant CXX update `.harness/conventions/` when the new requirement changes durable project rules.
92
+
82
93
  ---
83
94
 
84
95
  ## Hard Rules
@@ -93,7 +104,7 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
93
104
  | 6 | CEO routes only to CXX — never directly to workers |
94
105
  | 7 | No CXX self-execution — deliverables without matching worker records are rejected |
95
106
  | 8 | No verdict without worker evidence — CQO self-inspection is not valid |
96
- | 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{mission}/{owning-cxx}/workers/` |
107
+ | 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/` |
97
108
  | 10 | Implementation Notes required — `ceo.md`, every `{cxx}.md`, and every worker report must end with an English `## Implementation Notes` section |
98
109
 
99
110
  ### Implementation Notes Format
@@ -169,9 +180,10 @@ npx walwal-harness init --force --project-root /path/to/project
169
180
 
170
181
  Features:
171
182
 
172
- - **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy
173
- - **Mission Timeline** — clickable history of goal/hot-fix missions showing the full dispatch chain
174
- - **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → worker files changed → CQO verdict
183
+ - **Split Workspace** — full-viewport left/right panes keep Org Tree + Mission Timeline beside the selected mission detail; both panes scroll independently with hidden scrollbars, and the detail pane resets to top on selection changes
184
+ - **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy, with worker cards grouped under their owning CXX
185
+ - **Mission Timeline** — clickable history of goal/submission/hot-fix missions showing the full dispatch chain
186
+ - **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → owner-specific worker evidence → CQO verdict
175
187
  - **History tab** — mission-specific Owner request (from CEO summary + closest progress.log match)
176
188
  - **Gotchas tab** — searchable `.harness/gotchas/*.md` knowledge base, click to read full markdown
177
189
  - **Document tab** — per-CXX markdown doc viewer
@@ -182,8 +194,10 @@ Features:
182
194
 
183
195
  | Path | Role |
184
196
  |---|---|
185
- | `.harness/documents/{mission}/` | CXX decisions and worker reports per mission |
186
- | `.harness/documents/{mission}/{cxx}/workers/` | Worker reports owned by that CXX |
197
+ | `.harness/documents/goal-{index}-{name}/` | Goal-level CXX decisions and worker reports |
198
+ | `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/` | Additional requirement under the active goal |
199
+ | `.harness/documents/goal-{index}-{name}/hotfix-{index}-{name}/` | Emergency fix under the active goal |
200
+ | `.harness/documents/{goal-or-child-mission}/{cxx}/workers/` | Worker reports owned by that CXX |
187
201
  | `.harness/conventions/` | Durable rules (CQO writes, survives missions) |
188
202
  | `.harness/gotchas/` | Recurrence-prevention records (CQO registers per hot-fix) |
189
203
  | `.harness/shared/HR-Resource/` | Hireable worker skill pool |
@@ -208,6 +222,10 @@ harness-hiring → register and onboard worker
208
222
 
209
223
  | Version | Summary |
210
224
  |---|---|
225
+ | 7.1.13 | Dashboard: full-viewport split workspace, hidden pane scrollbars, and detail scroll reset on selection |
226
+ | 7.1.12 | Dashboard: split workspace panes now scroll independently |
227
+ | 7.1.11 | Dashboard: persistent 50/50 split workspace and CXX-owned worker grouping in the org tree |
228
+ | 7.1.10 | Adds `/submission` and nested goal/submission/hot-fix mission history |
211
229
  | 7.1.9 | Dashboard is packaged with npm and auto-syncs isolated cache by package version; README documents dashboard port/reinstall and update/migrate commands |
212
230
  | 7.1.8 | HR-Resource: replace China-platform-specific workers with global equivalents; refine cross-border/video/livestream skills |
213
231
  | 7.1.7 | Implementation Notes mandatory in all CXX docs and worker reports; harness-worker-evidence-validate.sh |
@@ -15,3 +15,12 @@ body {
15
15
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
16
16
  sans-serif;
17
17
  }
18
+
19
+ .scrollbar-hidden {
20
+ scrollbar-width: none;
21
+ -ms-overflow-style: none;
22
+ }
23
+
24
+ .scrollbar-hidden::-webkit-scrollbar {
25
+ display: none;
26
+ }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useEffect } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
 
4
4
  export type DrawerTab =
5
5
  | "mission-flow" // Mission document hierarchy (PRIMARY)
@@ -15,6 +15,8 @@ interface DrawerProps {
15
15
  onClose: () => void;
16
16
  onTabChange: (tab: DrawerTab) => void;
17
17
  children: React.ReactNode;
18
+ mode?: "overlay" | "inline";
19
+ scrollResetKey?: string;
18
20
  }
19
21
 
20
22
  const TABS: Array<{ id: DrawerTab; label: string }> = [
@@ -25,15 +27,60 @@ const TABS: Array<{ id: DrawerTab; label: string }> = [
25
27
  { id: "logs", label: "Agent Log" },
26
28
  ];
27
29
 
28
- export function Drawer({ open, tab, title, onClose, onTabChange, children }: DrawerProps) {
30
+ export function Drawer({ open, tab, title, onClose, onTabChange, children, mode = "overlay", scrollResetKey }: DrawerProps) {
31
+ const contentRef = useRef<HTMLDivElement | null>(null);
32
+
29
33
  useEffect(() => {
30
- if (!open) return;
34
+ if (!open || mode === "inline") return;
31
35
  const onKey = (e: KeyboardEvent) => {
32
36
  if (e.key === "Escape") onClose();
33
37
  };
34
38
  window.addEventListener("keydown", onKey);
35
39
  return () => window.removeEventListener("keydown", onKey);
36
- }, [open, onClose]);
40
+ }, [open, onClose, mode]);
41
+
42
+ useEffect(() => {
43
+ contentRef.current?.scrollTo({ top: 0, left: 0 });
44
+ }, [scrollResetKey]);
45
+
46
+ if (mode === "inline") {
47
+ return (
48
+ <aside
49
+ role="complementary"
50
+ aria-label="Detail panel"
51
+ data-testid="drawer"
52
+ data-open="true"
53
+ className="h-full min-h-0 rounded-md border border-brick-wall bg-brick-bg shadow-2xl flex flex-col overflow-hidden"
54
+ >
55
+ <header className="flex items-center justify-between border-b border-brick-wall px-4 py-3">
56
+ <h2 className="text-sm font-mono uppercase tracking-widest text-gray-200 truncate">
57
+ {title}
58
+ </h2>
59
+ </header>
60
+ <nav className="flex flex-wrap border-b border-brick-wall text-[11px] font-mono">
61
+ {TABS.map((t) => (
62
+ <button
63
+ key={t.id}
64
+ type="button"
65
+ data-testid={`drawer-tab-${t.id}`}
66
+ data-active={t.id === tab}
67
+ onClick={() => onTabChange(t.id)}
68
+ className={`min-w-[33%] flex-1 px-2 py-2 transition-colors ${
69
+ t.id === tab
70
+ ? "bg-brick-wall/50 text-gray-100 border-b-2 border-aura-typing"
71
+ : "text-gray-500 hover:text-gray-200"
72
+ }`}
73
+ >
74
+ {t.label}
75
+ </button>
76
+ ))}
77
+ </nav>
78
+ <div ref={contentRef} className="scrollbar-hidden flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
79
+ {children}
80
+ </div>
81
+ </aside>
82
+ );
83
+ }
37
84
 
38
85
  return (
39
86
  <>
@@ -88,7 +135,7 @@ export function Drawer({ open, tab, title, onClose, onTabChange, children }: Dra
88
135
  </button>
89
136
  ))}
90
137
  </nav>
91
- <div className="flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
138
+ <div ref={contentRef} className="scrollbar-hidden flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
92
139
  {children}
93
140
  </div>
94
141
  </aside>
@@ -153,7 +153,9 @@ export function MissionTimeline({
153
153
  const typeBadge =
154
154
  mission.type === "hotfix"
155
155
  ? "[🔥 hot-fix]"
156
- : mission.type === "feature"
156
+ : mission.type === "submission"
157
+ ? "[+ submission]"
158
+ : mission.type === "goal" || mission.type === "feature"
157
159
  ? "[✦ goal]"
158
160
  : "[— unknown]";
159
161
  const shortId =
@@ -188,6 +190,8 @@ export function MissionTimeline({
188
190
  className={`shrink-0 font-mono text-[9px] rounded px-1.5 py-0.5 border ${
189
191
  mission.type === "hotfix"
190
192
  ? "bg-rose-500/15 text-rose-300 border-rose-500/30"
193
+ : mission.type === "submission"
194
+ ? "bg-sky-500/15 text-sky-300 border-sky-500/30"
191
195
  : "bg-emerald-500/15 text-emerald-300 border-emerald-500/30"
192
196
  }`}
193
197
  >
@@ -129,7 +129,7 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
129
129
  id: "owner",
130
130
  role: "owner",
131
131
  label: "Owner",
132
- sublabel: "/goal · /hot-fix",
132
+ sublabel: "/goal · /submission · /hot-fix",
133
133
  status: "idle",
134
134
  activity: snapshot.ownerHistory?.[0]?.content.slice(0, 80) ?? null,
135
135
  agentIds: [],
@@ -225,7 +225,8 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
225
225
  },
226
226
  ];
227
227
 
228
- // Workers from current mission's workers/, shown under CTO column
228
+ // Workers are grouped by their owning CXX so evaluator reports sit under CQO,
229
+ // implementation reports under CTO, and legacy flat reports stay visible.
229
230
  const workers = currentMission?.workers ?? [];
230
231
 
231
232
  return (
@@ -271,7 +272,9 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
271
272
  {/* Row 3: CXX nodes with workers below */}
272
273
  <div className="flex gap-3 justify-center">
273
274
  {cxxNodes.map((node) => {
274
- const nodeWorkers = node.role === "cto" ? workers : [];
275
+ const nodeWorkers = workers.filter(
276
+ (w) => w.owner === node.role || (w.owner === "unknown" && node.role === "cto")
277
+ );
275
278
 
276
279
  return (
277
280
  <div key={node.id} className="flex flex-col items-center gap-2">
@@ -293,7 +296,7 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
293
296
  onClick={() =>
294
297
  onNodeClick({
295
298
  id: `worker-${w.name}`,
296
- role: "cto",
299
+ role: node.role,
297
300
  label: w.name,
298
301
  sublabel: w.status,
299
302
  status: w.status === "COMPLETE" ? "idle" : "typing",
@@ -27,7 +27,6 @@ interface SceneProps {
27
27
 
28
28
  export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
29
29
  const { snapshot, connectionState } = useHarnessStream(initial);
30
- const [drawerOpen, setDrawerOpen] = useState(false);
31
30
  const [drawerTab, setDrawerTab] = useState<DrawerTab>("mission-flow");
32
31
  const [selectedAgent, setSelectedAgent] = useState<AgentState | null>(null);
33
32
  const [selectedNode, setSelectedNode] = useState<OrgNodeDef | null>(null);
@@ -38,7 +37,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
38
37
  setSelectedAgent(a);
39
38
  setSelectedNode(null);
40
39
  setDrawerTab("logs");
41
- setDrawerOpen(true);
42
40
  };
43
41
 
44
42
  const handleNodeClick = (node: OrgNodeDef) => {
@@ -55,7 +53,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
55
53
  } else {
56
54
  setDrawerTab("mission-flow");
57
55
  }
58
- setDrawerOpen(true);
59
56
  };
60
57
 
61
58
  const handleMissionClick = (mission: MissionDoc) => {
@@ -63,7 +60,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
63
60
  setSelectedNode(null);
64
61
  setSelectedAgent(null);
65
62
  setDrawerTab("mission-flow");
66
- setDrawerOpen(true);
67
63
  };
68
64
 
69
65
  const drawerTitle =
@@ -74,60 +70,126 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
74
70
  : selectedNode
75
71
  ? selectedNode.label
76
72
  : "Mission Flow";
73
+ const drawerScrollResetKey = [
74
+ drawerTab,
75
+ selectedMission?.missionId ?? "",
76
+ selectedNode?.id ?? "",
77
+ selectedAgent?.id ?? "",
78
+ ].join(":");
77
79
 
78
80
  // Suppress unused lang warning — kept for future i18n
79
81
  void lang;
80
82
 
81
83
  return (
82
- <div className="mx-auto max-w-[1920px] px-4 py-5">
84
+ <div className="flex h-[100dvh] w-full flex-col overflow-hidden px-4 py-5">
83
85
  {snapshot.escalations.length > 0 && (
84
86
  <EscalationStrip escalations={snapshot.escalations} />
85
87
  )}
86
- <div className="min-w-0">
87
- <section className="min-w-0">
88
- <div className="mb-3 flex flex-wrap items-end justify-between gap-3">
89
- <div>
90
- <p className="text-[11px] font-mono uppercase tracking-[0.24em] text-cyan-300/80">
91
- Company Structure
92
- </p>
93
- <h1 className="mt-1 text-2xl font-semibold text-gray-100">
94
- {snapshot.projectName || "walwal-harness"} · Organization
95
- </h1>
88
+ <div className="grid min-h-0 flex-1 min-w-0 gap-4 lg:grid-cols-2">
89
+ <div className="scrollbar-hidden min-h-0 min-w-0 overflow-y-auto pr-1">
90
+ <section className="min-w-0">
91
+ <div className="mb-3 flex flex-wrap items-end justify-between gap-3">
92
+ <div>
93
+ <p className="text-[11px] font-mono uppercase tracking-[0.24em] text-cyan-300/80">
94
+ Company Structure
95
+ </p>
96
+ <h1 className="mt-1 text-2xl font-semibold text-gray-100">
97
+ {snapshot.projectName || "walwal-harness"} · Organization
98
+ </h1>
99
+ </div>
100
+ <ActivityIndicator snapshot={snapshot} connectionState={connectionState} />
96
101
  </div>
97
- <ActivityIndicator snapshot={snapshot} connectionState={connectionState} />
98
- </div>
99
- <div
100
- data-testid="brick-office-stage"
101
- className="w-full overflow-hidden rounded-md border border-gray-700/80 bg-[#12151b] shadow-2xl"
102
- >
103
- <div className="pointer-events-none flex items-center justify-between border-b border-white/10 bg-black/30 px-4 py-2 font-mono text-[10px] text-gray-300 backdrop-blur">
104
- <span>Owner → CEO → CXX → Workers</span>
105
- <span>
106
- {snapshot.missions?.[0]?.missionId ?? "no active mission"}
107
- {snapshot.missions?.[0]?.cxxPresent.length
108
- ? ` · ${snapshot.missions[0].cxxPresent.join(" / ")}`
109
- : ""}
110
- </span>
102
+ <div
103
+ data-testid="brick-office-stage"
104
+ className="w-full overflow-x-auto rounded-md border border-gray-700/80 bg-[#12151b] shadow-2xl"
105
+ >
106
+ <div className="pointer-events-none flex items-center justify-between gap-3 border-b border-white/10 bg-black/30 px-4 py-2 font-mono text-[10px] text-gray-300 backdrop-blur">
107
+ <span>Owner → CEO → CXX → Workers</span>
108
+ <span className="truncate">
109
+ {snapshot.missions?.[0]?.missionId ?? "no active mission"}
110
+ {snapshot.missions?.[0]?.cxxPresent.length
111
+ ? ` · ${snapshot.missions[0].cxxPresent.join(" / ")}`
112
+ : ""}
113
+ </span>
114
+ </div>
115
+ <OrgTree
116
+ snapshot={snapshot}
117
+ activeNodeId={selectedNode?.id ?? null}
118
+ onNodeClick={handleNodeClick}
119
+ />
120
+ </div>
121
+ </section>
122
+
123
+ <section className="mt-4">
124
+ <div className="mb-2">
125
+ <p className="font-mono text-[10px] uppercase tracking-[0.2em] text-cyan-300/60">Mission History</p>
126
+ <p className="text-[11px] text-gray-500 mt-0.5">goal · submission · hot-fix 명령별 파생 작업 흐름</p>
111
127
  </div>
112
- <OrgTree
113
- snapshot={snapshot}
114
- activeNodeId={selectedNode?.id ?? null}
115
- onNodeClick={handleNodeClick}
128
+ <MissionTimeline
129
+ missions={snapshot.missions ?? []}
130
+ activeMissionId={selectedMission?.missionId ?? null}
131
+ onMissionClick={handleMissionClick}
116
132
  />
117
- </div>
118
- </section>
133
+ </section>
134
+ </div>
119
135
 
120
- <section className="mt-4">
121
- <div className="mb-2">
122
- <p className="font-mono text-[10px] uppercase tracking-[0.2em] text-cyan-300/60">Mission History</p>
123
- <p className="text-[11px] text-gray-500 mt-0.5">goal · hot-fix 명령별 파생 작업 흐름</p>
124
- </div>
125
- <MissionTimeline
126
- missions={snapshot.missions ?? []}
127
- activeMissionId={selectedMission?.missionId ?? null}
128
- onMissionClick={handleMissionClick}
129
- />
130
- </section>
136
+ <div className="min-h-0 min-w-0">
137
+ <Drawer
138
+ mode="inline"
139
+ open={true}
140
+ tab={drawerTab}
141
+ title={drawerTitle}
142
+ onClose={() => undefined}
143
+ onTabChange={setDrawerTab}
144
+ scrollResetKey={drawerScrollResetKey}
145
+ >
146
+ {drawerTab === "mission-flow" && (
147
+ <MissionFlowTab
148
+ mission={selectedMission ?? snapshot.missions?.[0] ?? null}
149
+ ownerHistory={snapshot.ownerHistory ?? []}
150
+ />
151
+ )}
152
+ {drawerTab === "history" && (
153
+ <OwnerHistoryTab
154
+ ownerHistory={snapshot.ownerHistory ?? []}
155
+ mission={selectedMission ?? snapshot.missions?.[0] ?? null}
156
+ />
157
+ )}
158
+ {drawerTab === "gotchas" && (
159
+ <GotchasTab gotchas={snapshot.gotchas ?? []} />
160
+ )}
161
+ {drawerTab === "mission-doc" && selectedNode && (
162
+ <MissionDocTab
163
+ missions={snapshot.missions ?? []}
164
+ role={
165
+ selectedNode.role === "owner"
166
+ ? "ceo"
167
+ : (selectedNode.role as "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops")
168
+ }
169
+ fromLabel={selectedNode.role === "ceo" ? "Owner" : "CEO"}
170
+ toLabel={
171
+ selectedNode.role === "cto"
172
+ ? "Workers"
173
+ : selectedNode.role === "cqo"
174
+ ? "Owner Report"
175
+ : "—"
176
+ }
177
+ />
178
+ )}
179
+ {drawerTab === "logs" && selectedNode && (
180
+ <AgentLogTab agentId={selectedNode.agentIds[0] ?? selectedNode.id} />
181
+ )}
182
+ {drawerTab === "logs" && selectedAgent && !selectedNode && (
183
+ <AgentLogTab agentId={selectedAgent.id} />
184
+ )}
185
+ {drawerTab === "logs" && !selectedNode && !selectedAgent && (
186
+ <div className="text-gray-500 text-xs">Select a node to view logs.</div>
187
+ )}
188
+ {drawerTab === "mission-flow" && !selectedMission && !snapshot.missions?.length && (
189
+ <div className="text-gray-500 text-xs">No missions found in .harness/documents/</div>
190
+ )}
191
+ </Drawer>
192
+ </div>
131
193
  </div>
132
194
 
133
195
  {/* Hidden DOM index for E2E + a11y */}
@@ -153,60 +215,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
153
215
  </li>
154
216
  ))}
155
217
  </ul>
156
-
157
- <Drawer
158
- open={drawerOpen}
159
- tab={drawerTab}
160
- title={drawerTitle}
161
- onClose={() => setDrawerOpen(false)}
162
- onTabChange={setDrawerTab}
163
- >
164
- {drawerTab === "mission-flow" && (
165
- <MissionFlowTab
166
- mission={selectedMission ?? snapshot.missions?.[0] ?? null}
167
- ownerHistory={snapshot.ownerHistory ?? []}
168
- />
169
- )}
170
- {drawerTab === "history" && (
171
- <OwnerHistoryTab
172
- ownerHistory={snapshot.ownerHistory ?? []}
173
- mission={selectedMission ?? snapshot.missions?.[0] ?? null}
174
- />
175
- )}
176
- {drawerTab === "gotchas" && (
177
- <GotchasTab gotchas={snapshot.gotchas ?? []} />
178
- )}
179
- {drawerTab === "mission-doc" && selectedNode && (
180
- <MissionDocTab
181
- missions={snapshot.missions ?? []}
182
- role={
183
- selectedNode.role === "owner"
184
- ? "ceo"
185
- : (selectedNode.role as "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops")
186
- }
187
- fromLabel={selectedNode.role === "ceo" ? "Owner" : "CEO"}
188
- toLabel={
189
- selectedNode.role === "cto"
190
- ? "Workers"
191
- : selectedNode.role === "cqo"
192
- ? "Owner Report"
193
- : "—"
194
- }
195
- />
196
- )}
197
- {drawerTab === "logs" && selectedNode && (
198
- <AgentLogTab agentId={selectedNode.agentIds[0] ?? selectedNode.id} />
199
- )}
200
- {drawerTab === "logs" && selectedAgent && !selectedNode && (
201
- <AgentLogTab agentId={selectedAgent.id} />
202
- )}
203
- {drawerTab === "logs" && !selectedNode && !selectedAgent && (
204
- <div className="text-gray-500 text-xs">Select a node to view logs.</div>
205
- )}
206
- {drawerTab === "mission-flow" && !selectedMission && !snapshot.missions?.length && (
207
- <div className="text-gray-500 text-xs">No missions found in .harness/documents/</div>
208
- )}
209
- </Drawer>
210
218
  </div>
211
219
  );
212
220
  }
@@ -288,7 +288,8 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
288
288
 
289
289
  const typeBadge =
290
290
  mission.type === "hotfix" ? "[🔥 hot-fix]"
291
- : mission.type === "feature" ? "[✦ goal]"
291
+ : mission.type === "submission" ? "[+ submission]"
292
+ : mission.type === "goal" || mission.type === "feature" ? "[✦ goal]"
292
293
  : "[— unknown]";
293
294
 
294
295
  let dateStr = "—";
@@ -341,6 +342,8 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
341
342
  className={`font-mono text-[9px] rounded border px-1.5 py-0.5 ${
342
343
  mission.type === "hotfix"
343
344
  ? "bg-rose-500/15 text-rose-300 border-rose-500/30"
345
+ : mission.type === "submission"
346
+ ? "bg-sky-500/15 text-sky-300 border-sky-500/30"
344
347
  : "bg-emerald-500/15 text-emerald-300 border-emerald-500/30"
345
348
  }`}
346
349
  >
@@ -92,6 +92,8 @@ export function OwnerHistoryTab({ ownerHistory, mission, onEntryClick }: Props)
92
92
  className={`font-mono text-[9px] px-1.5 py-0.5 rounded ${
93
93
  closestEntry.type === "goal"
94
94
  ? "bg-emerald-500/15 text-emerald-300"
95
+ : closestEntry.type === "submission"
96
+ ? "bg-sky-500/15 text-sky-300"
95
97
  : closestEntry.type === "hot-fix"
96
98
  ? "bg-rose-500/15 text-rose-300"
97
99
  : "bg-gray-500/15 text-gray-400"
@@ -140,6 +142,8 @@ export function OwnerHistoryTab({ ownerHistory, mission, onEntryClick }: Props)
140
142
  className={`font-mono text-[9px] px-1.5 py-0.5 rounded ${
141
143
  entry.type === "goal"
142
144
  ? "bg-emerald-500/15 text-emerald-300"
145
+ : entry.type === "submission"
146
+ ? "bg-sky-500/15 text-sky-300"
143
147
  : entry.type === "hot-fix"
144
148
  ? "bg-rose-500/15 text-rose-300"
145
149
  : "bg-gray-500/15 text-gray-400"