gigaplan 0.1.0 → 0.2.0

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 CHANGED
@@ -7,6 +7,9 @@ sections with per-step inline comments, a review-coverage sidebar, and a
7
7
  finish-your-review panel (Approve / Approve with comments / Request changes),
8
8
  then hands the structured feedback back to the agent to act on.
9
9
 
10
+ [gigaplan on npm](https://www.npmjs.com/package/gigaplan) ·
11
+ [gigaplan on skills.sh](https://www.skills.sh/corradodellorusso/gigaplan/gigaplan)
12
+
10
13
  The UI follows the "Atelier / Terminal" design system (warm-paper light theme,
11
14
  phosphor-terminal dark theme, Bricolage Grotesque + JetBrains Mono type) —
12
15
  see `skills/gigaplan/SKILL.md` for the review workflow and `public/chrome.css`
@@ -14,49 +17,40 @@ for the theme itself.
14
17
 
15
18
  ## Getting started
16
19
 
17
- gigaplan isn't published to npm yet, so for now it runs from a local checkout
18
- of this repo:
20
+ No install needed try it against any Markdown file:
19
21
 
20
22
  ```
21
- npm install # also builds it — see the `prepare` script
22
- ```
23
-
24
- Try it against any Markdown file:
25
-
26
- ```
27
- node bin/gigaplan.js review path/to/plan.md
23
+ npx -y gigaplan review path/to/plan.md
28
24
  ```
29
25
 
30
26
  That opens your browser straight to the review page. Leave a few comments,
31
27
  pick a verdict, and submit — then, in a second terminal:
32
28
 
33
29
  ```
34
- node bin/gigaplan.js poll path/to/plan.md
30
+ npx -y gigaplan poll path/to/plan.md
35
31
  ```
36
32
 
37
33
  blocks until you do, then prints back exactly the compact-markdown feedback an
38
- agent would read. `gigaplan end path/to/plan.md` marks the session done
34
+ agent would read. `npx -y gigaplan end path/to/plan.md` marks the session done
39
35
  afterward (the shared local server itself stays running independently — see
40
36
  Configuration below).
41
37
 
42
- Optionally, run `npm link` once if you'd rather type the bare `gigaplan`
43
- command than the `node bin/gigaplan.js` prefix.
44
-
45
38
  ### Installing the skill
46
39
 
47
40
  The point of gigaplan is for an *agent* — any agent, not tied to one product —
48
41
  to drive this loop on your behalf: write the plan, open it for you, wait for
49
42
  your review, and revise based on your comments, looping until you approve it.
50
- Install `skills/gigaplan/SKILL.md` via skills.sh so it's picked up
51
- automatically, without you needing to invoke anything by name.
43
+ Install the skill so it's picked up automatically, without you needing to
44
+ invoke anything by name:
45
+
46
+ ```
47
+ npx skills add https://github.com/corradodellorusso/gigaplan --skill gigaplan
48
+ ```
49
+
52
50
  `skills/gigaplan/SKILL.md` documents the exact workflow and CLI contract an
53
51
  agent follows — read it if you want to know precisely what happens under the
54
52
  hood.
55
53
 
56
- Once gigaplan is published, `npx -y gigaplan ...` per command replaces the
57
- local-checkout commands above — no local build needed, matching what the
58
- skill already documents.
59
-
60
54
  ## How it works
61
55
 
62
56
  ```
@@ -85,9 +79,6 @@ gigaplan end <path> Mark the review session done (the shared local server s
85
79
  it shuts itself down after a period of inactivity).
86
80
  ```
87
81
 
88
- Locally (pre-publish), that's `node bin/gigaplan.js <command>` unless you've
89
- `npm link`-ed it (see Getting started).
90
-
91
82
  ## Configuration
92
83
 
93
84
  | Env var | Default | Purpose |
@@ -99,6 +90,13 @@ State (session records and the server's pid/port lock) lives in `~/.gigaplan/`.
99
90
 
100
91
  ## Contributing
101
92
 
93
+ Working from a local checkout instead of the published package:
94
+
95
+ ```
96
+ npm install # also builds it — see the `prepare` script
97
+ node bin/gigaplan.js review path/to/plan.md # or `npm link` once, then plain `gigaplan ...`
98
+ ```
99
+
102
100
  ```
103
101
  npm run typecheck # tsc --noEmit against both the server and client TypeScript
104
102
  npm test # node:test, run directly against .ts sources via tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gigaplan",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Review agent-authored implementation plans in a GitHub-PR-style browser UI. Agents write plain Markdown; gigaplan renders, themes, and collects structured feedback.",
5
5
  "type": "module",
6
6
  "bin": {
package/public/chrome.css CHANGED
@@ -1,146 +1,159 @@
1
1
  /* ============================================================
2
- gigaplan chrome — "Atelier / Terminal" theme
3
- Ported from the Corrado dello Russo design system
4
- (colors/typography/spacing/radius/elevation/motion/base tokens
5
- + the subset of cdr- component classes gigaplan actually uses).
2
+ gigaplan chrome — "Belief" theme
3
+ Ported from the "Belief Design System" (a from-scratch Notion-style
4
+ document-editor system authored via the claude_design MCP — tokens,
5
+ type, spacing, effects, and the subset of component patterns gigaplan
6
+ actually uses: Button, IconButton, Checkbox, Avatar, Comment, Callout,
7
+ CodeBlock, Quote, Divider, ListItem, Table).
8
+ Belief itself ships light-only ("near-black-and-white... flat and
9
+ white backgrounds"); the dark-theme values below are gigaplan's own
10
+ addition, built by inverting Belief's semantic tokens (near-white text
11
+ at the same opacities, surfaces flipped to near-black, one accent blue
12
+ tuned for contrast) since gigaplan needs both themes and Belief's
13
+ source didn't supply one.
6
14
  ============================================================ */
7
15
 
8
- @import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
16
+ @import url("https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap");
9
17
 
10
18
  /* ---------------------------------------------------------- COLOR TOKENS */
11
19
  :root {
12
- --bone-0: #FBF7EF; --bone-50: #F8F3E9; --bone-100: #F1EBDF; --bone-200: #E8E0D0;
13
- --bone-300: #DDD2BE; --bone-400: #CBBCA1; --bone-500: #A8987F; --bone-600: #8C7F6B;
14
- --bone-700: #5E5648; --bone-800: #3A342B; --bone-900: #26221B; --bone-950: #1B1714;
15
-
16
- --coal-0: #EDEBE0; --coal-50: #C9C8BB; --coal-100: #9A9C8E; --coal-200: #777A6C;
17
- --coal-300: #585B4E; --coal-400: #3C3F33; --coal-500: #2A2C22; --coal-600: #1F2018;
18
- --coal-700: #161710; --coal-800: #0F100B; --coal-900: #0B0C0A; --coal-950: #070803;
19
-
20
- --vermilion-50: #FBEAE3; --vermilion-400: #E4633F; --vermilion-500: #E0421F;
21
- --vermilion-600: #C5371A; --vermilion-700: #A42C14;
22
-
23
- --amber-300: #F6C460; --amber-400: #F4B23E; --amber-500: #E89A1E;
24
-
25
- --green-300: #97CBAB; --green-400: #6FB58A; --green-500: #4E9A6E;
26
- --green-600: #3C7C57; --green-700: #2C5C40;
27
-
28
- --inkblue-500: #2A3F66; --inkblue-600: #1F3052;
29
-
30
- --crimson-100: #F4D2CE; --crimson-500: #B3261E; --crimson-600: #971F18;
31
-
32
- --warn-100: #F6E6C4; --warn-500: #C2871A; --warn-600: #9C6C12;
20
+ --gray-0: #ffffff;
21
+ --gray-25: #fbfbfa;
22
+ --gray-50: #f7f6f5;
23
+ --gray-100: #f1f1ef;
24
+ --gray-150: #e9e9e7;
25
+ --gray-200: #e3e2e0;
26
+ --gray-300: #d3d1cb;
27
+ --gray-400: #b8b6ae;
28
+ --gray-500: #9b9a97;
29
+ --gray-600: #787774;
30
+ --gray-700: #5f5e5b;
31
+ --gray-800: #454340;
32
+ --gray-900: #2f2e2b;
33
+ --gray-950: #191919;
34
+
35
+ --avatar-1: #d9730d;
36
+ --avatar-2: #0f7b6c;
37
+ --avatar-3: #0b6e99;
38
+ --avatar-4: #6940a5;
39
+ --avatar-5: #ad1a72;
40
+ --avatar-6: #2a8a4a;
33
41
  }
34
42
 
35
43
  :root, [data-theme="light"] {
36
44
  color-scheme: light;
37
- --bg: var(--bone-100);
38
- --surface: var(--bone-50);
39
- --surface-2: var(--bone-200);
40
- --surface-3: var(--bone-300);
41
- --surface-inset: var(--bone-200);
42
- --text: var(--bone-950);
43
- --text-muted: var(--bone-700);
44
- --text-subtle: var(--bone-600);
45
- --text-on-accent: var(--bone-0);
46
- --border: color-mix(in oklab, var(--bone-950) 14%, transparent);
47
- --border-strong: color-mix(in oklab, var(--bone-950) 28%, transparent);
48
- --ring: var(--vermilion-500);
49
- --primary: var(--vermilion-500);
50
- --primary-hover: var(--vermilion-600);
51
- --primary-press: var(--vermilion-700);
52
- --primary-soft: var(--vermilion-50);
53
- --primary-text: var(--vermilion-700);
54
- --accent: var(--inkblue-500);
55
- --accent-soft: color-mix(in oklab, var(--inkblue-500) 12%, var(--bone-50));
56
- --accent-text: var(--inkblue-600);
57
- --success: var(--green-600);
58
- --success-soft: color-mix(in oklab, var(--green-500) 18%, var(--bone-50));
59
- --success-text: var(--green-700);
60
- --warning: var(--warn-500);
61
- --warning-soft: var(--warn-100);
62
- --warning-text: var(--warn-600);
63
- --danger: var(--crimson-500);
64
- --danger-soft: var(--crimson-100);
65
- --danger-text: var(--crimson-600);
66
- --glow: none;
45
+
46
+ --text-primary: rgba(25, 25, 25, 0.92);
47
+ --text-secondary: rgba(25, 25, 25, 0.65);
48
+ --text-tertiary: rgba(25, 25, 25, 0.45);
49
+ --text-disabled: rgba(25, 25, 25, 0.28);
50
+
51
+ --surface-page: #ffffff;
52
+ --surface-sidebar: #f7f6f5;
53
+ --surface-card: #ffffff;
54
+ --surface-hover: rgba(25, 25, 25, 0.055);
55
+ --surface-active: rgba(25, 25, 25, 0.09);
56
+ --surface-overlay: rgba(15, 15, 15, 0.45);
57
+ --surface-code: #f7f6f5;
58
+
59
+ --border-subtle: rgba(25, 25, 25, 0.09);
60
+ --border-default: rgba(25, 25, 25, 0.13);
61
+ --border-strong: rgba(25, 25, 25, 0.22);
62
+
63
+ --accent: #2383e2;
64
+ --accent-hover: #0b6fcc;
65
+ --accent-tint: rgba(35, 131, 226, 0.12);
66
+
67
+ --status-success: #2a8a4a;
68
+ --status-success-tint: #edf6ee;
69
+ --status-warning: #b8740a;
70
+ --status-warning-tint: #fbf1e0;
71
+ --status-danger: #d13c3c;
72
+ --status-danger-tint: #fceeee;
73
+
74
+ /* "solid near-black" chrome (primary buttons, checked controls, logo
75
+ mark, tooltips, quote rule) — inverted below for dark mode. */
76
+ --inverse-surface: var(--gray-950);
77
+ --inverse-surface-hover: var(--gray-800);
78
+ --inverse-text: #ffffff;
79
+
80
+ --shadow-xs: 0 0 0 1px rgba(15, 15, 15, 0.05);
81
+ --shadow-sm: 0 1px 2px rgba(15, 15, 15, 0.08), 0 0 0 1px rgba(15, 15, 15, 0.04);
82
+ --shadow-md: 0 3px 6px rgba(15, 15, 15, 0.1), 0 1px 3px rgba(15, 15, 15, 0.08);
83
+ --shadow-lg: 0 12px 28px rgba(15, 15, 15, 0.14), 0 2px 4px rgba(15, 15, 15, 0.08);
84
+ --shadow-focus-ring: 0 0 0 2px rgba(35, 131, 226, 0.35);
67
85
  }
68
86
 
69
87
  [data-theme="dark"] {
70
88
  color-scheme: dark;
71
- --bg: var(--coal-900);
72
- --surface: var(--coal-800);
73
- --surface-2: var(--coal-700);
74
- --surface-3: var(--coal-600);
75
- --surface-inset: var(--coal-950);
76
- --text: var(--coal-0);
77
- --text-muted: var(--coal-100);
78
- --text-subtle: var(--coal-200);
79
- --text-on-accent: var(--coal-950);
80
- --border: color-mix(in oklab, var(--coal-0) 14%, transparent);
81
- --border-strong: color-mix(in oklab, var(--coal-0) 26%, transparent);
82
- --ring: var(--amber-400);
83
- --primary: var(--amber-400);
84
- --primary-hover: var(--amber-300);
85
- --primary-press: var(--amber-300);
86
- --primary-soft: color-mix(in oklab, var(--amber-400) 16%, var(--coal-800));
87
- --primary-text: var(--amber-300);
88
- --accent: var(--green-400);
89
- --accent-soft: color-mix(in oklab, var(--green-400) 16%, var(--coal-800));
90
- --accent-text: var(--green-300);
91
- --success: var(--green-400);
92
- --success-soft: color-mix(in oklab, var(--green-400) 16%, var(--coal-800));
93
- --success-text: var(--green-300);
94
- --warning: var(--amber-400);
95
- --warning-soft: color-mix(in oklab, var(--amber-400) 16%, var(--coal-800));
96
- --warning-text: var(--amber-300);
97
- --danger: #E5604D;
98
- --danger-soft: color-mix(in oklab, #E5604D 16%, var(--coal-800));
99
- --danger-text: #F0937F;
100
- --glow: 0 0 22px color-mix(in oklab, var(--amber-400) 35%, transparent);
101
- }
102
-
103
- /* ---------------------------------------------------------- TYPE + SPACING + RADIUS + ELEVATION + MOTION */
89
+
90
+ --text-primary: rgba(255, 255, 255, 0.92);
91
+ --text-secondary: rgba(255, 255, 255, 0.65);
92
+ --text-tertiary: rgba(255, 255, 255, 0.48);
93
+ --text-disabled: rgba(255, 255, 255, 0.28);
94
+
95
+ --surface-page: #191919;
96
+ --surface-sidebar: #202020;
97
+ --surface-card: #1f1f1e;
98
+ --surface-hover: rgba(255, 255, 255, 0.06);
99
+ --surface-active: rgba(255, 255, 255, 0.1);
100
+ --surface-overlay: rgba(0, 0, 0, 0.6);
101
+ --surface-code: #212120;
102
+
103
+ --border-subtle: rgba(255, 255, 255, 0.09);
104
+ --border-default: rgba(255, 255, 255, 0.14);
105
+ --border-strong: rgba(255, 255, 255, 0.24);
106
+
107
+ --accent: #59a9f4;
108
+ --accent-hover: #85c1f8;
109
+ --accent-tint: rgba(89, 169, 244, 0.16);
110
+
111
+ --status-success: #4fae6f;
112
+ --status-success-tint: rgba(79, 174, 111, 0.14);
113
+ --status-warning: #d99a3d;
114
+ --status-warning-tint: rgba(217, 154, 61, 0.14);
115
+ --status-danger: #e5604d;
116
+ --status-danger-tint: rgba(229, 96, 77, 0.14);
117
+
118
+ --inverse-surface: #f2f2f0;
119
+ --inverse-surface-hover: #d3d1cb;
120
+ --inverse-text: #191919;
121
+
122
+ --shadow-xs: 0 0 0 1px rgba(0, 0, 0, 0.5);
123
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.35);
124
+ --shadow-md: 0 3px 6px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.4);
125
+ --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.55), 0 2px 4px rgba(0, 0, 0, 0.4);
126
+ --shadow-focus-ring: 0 0 0 2px rgba(89, 169, 244, 0.4);
127
+ }
128
+
129
+ /* ---------------------------------------------------------- TYPE + SPACING + EFFECTS */
104
130
  :root {
105
- --font-display: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
106
- --font-sans: "Bricolage Grotesque", ui-sans-serif, system-ui, -apple-system, sans-serif;
107
- --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
108
- --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700; --fw-black: 800;
109
-
110
- --text-2xs: 0.6875rem; --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem;
111
- --text-md: 1.125rem; --text-lg: 1.4rem; --text-xl: 1.8rem; --text-2xl: 2.35rem;
112
- --leading-tight: 0.96; --leading-normal: 1.5;
113
- --tracking-tighter: -0.045em; --tracking-tight: -0.025em; --tracking-label: 0.14em;
114
-
115
- --space-1: 0.25rem; --space-1_5: 0.375rem; --space-2: 0.5rem; --space-2_5: 0.625rem;
116
- --space-3: 0.75rem; --space-4: 1rem; --space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem;
117
-
118
- --radius-xs: 2px; --radius-sm: 3px; --radius-md: 5px; --radius-lg: 8px;
119
- --radius-control: var(--radius-sm); --radius-card: var(--radius-md);
120
-
121
- --shadow-sm: 0 1px 2px color-mix(in oklab, var(--bone-950) 7%, transparent);
122
- --shadow-md: 0 4px 12px color-mix(in oklab, var(--bone-950) 9%, transparent);
123
- --ring-width: 3px;
124
- --ring-color: color-mix(in oklab, var(--ring) 40%, transparent);
125
- --focus-ring: 0 0 0 var(--ring-width) var(--ring-color);
126
-
127
- --dur-fast: 140ms; --dur-base: 200ms;
128
- --ease-standard: cubic-bezier(0.2, 0, 0.1, 1);
129
- --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
130
- --transition-control: color var(--dur-fast) var(--ease-standard),
131
- background-color var(--dur-fast) var(--ease-standard),
132
- border-color var(--dur-fast) var(--ease-standard),
133
- box-shadow var(--dur-fast) var(--ease-standard),
134
- transform var(--dur-fast) var(--ease-standard);
135
- }
131
+ --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
132
+ --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
136
133
 
137
- [data-theme="dark"] {
138
- --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.5);
139
- --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.55);
134
+ --text-xs: 12px; --text-sm: 13px; --text-base: 15px; --text-md: 16px; --text-lg: 18px; --text-xl: 20px;
135
+ --heading-h3: 20px; --heading-h2: 24px; --heading-h1: 30px; --heading-title: 40px;
136
+
137
+ --leading-tight: 1.2; --leading-snug: 1.35; --leading-normal: 1.5; --leading-relaxed: 1.65;
138
+ --tracking-tight: -0.01em; --tracking-normal: 0em; --tracking-wide: 0.02em;
139
+ --weight-regular: 400; --weight-medium: 500; --weight-semibold: 600; --weight-bold: 700;
140
+
141
+ --space-1: 2px; --space-2: 4px; --space-3: 6px; --space-4: 8px; --space-5: 10px;
142
+ --space-6: 12px; --space-7: 14px; --space-8: 16px; --space-10: 20px; --space-12: 24px;
143
+
144
+ --radius-sm: 4px; --radius-md: 6px; --radius-lg: 9px; --radius-xl: 12px; --radius-pill: 999px;
145
+
146
+ --ease-standard: cubic-bezier(0.2, 0, 0, 1);
147
+ --duration-fast: 100ms; --duration-base: 150ms; --duration-slow: 220ms;
148
+ --transition-control: background var(--duration-fast) var(--ease-standard),
149
+ border-color var(--duration-fast) var(--ease-standard),
150
+ color var(--duration-fast) var(--ease-standard),
151
+ box-shadow var(--duration-fast) var(--ease-standard),
152
+ transform var(--duration-fast) var(--ease-standard);
140
153
  }
141
154
 
142
155
  @media (prefers-reduced-motion: reduce) {
143
- :root { --dur-fast: 0ms; --dur-base: 0ms; }
156
+ :root { --duration-fast: 0ms; --duration-base: 0ms; --duration-slow: 0ms; }
144
157
  }
145
158
 
146
159
  /* ---------------------------------------------------------- BASE */
@@ -148,33 +161,15 @@
148
161
 
149
162
  body {
150
163
  margin: 0;
151
- background: var(--bg);
152
- color: var(--text);
164
+ background: var(--surface-page);
165
+ color: var(--text-primary);
153
166
  font-family: var(--font-sans);
154
167
  font-size: var(--text-base);
155
168
  line-height: var(--leading-normal);
156
169
  -webkit-font-smoothing: antialiased;
157
170
  }
158
171
 
159
- h1, h2, h3 {
160
- font-family: var(--font-display);
161
- font-weight: var(--fw-bold);
162
- line-height: var(--leading-tight);
163
- letter-spacing: var(--tracking-tight);
164
- margin: 0;
165
- color: var(--text);
166
- }
167
-
168
- :focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-xs); }
169
-
170
- .ds-eyebrow {
171
- font-family: var(--font-mono);
172
- font-size: var(--text-xs);
173
- font-weight: var(--fw-medium);
174
- letter-spacing: var(--tracking-label);
175
- text-transform: uppercase;
176
- color: var(--text-muted);
177
- }
172
+ :focus-visible { outline: none; box-shadow: var(--shadow-focus-ring); border-radius: var(--radius-sm); }
178
173
 
179
174
  ::-webkit-scrollbar { width: 10px; height: 10px; }
180
175
  ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 6px; }
@@ -182,40 +177,39 @@ h1, h2, h3 {
182
177
 
183
178
  /* ---------------------------------------------------------- BUTTON */
184
179
  .cdr-btn {
185
- display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
186
- font-family: var(--font-sans); font-weight: var(--fw-medium); font-size: var(--text-sm);
187
- line-height: 1; white-space: nowrap; border: 1px solid transparent;
188
- background: var(--primary); color: var(--text-on-accent);
189
- border-radius: var(--radius-control); padding: 0 var(--space-4); height: 38px;
180
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--space-3);
181
+ font-family: var(--font-sans); font-weight: var(--weight-medium); font-size: var(--text-sm);
182
+ line-height: 1; white-space: nowrap; border: 1px solid var(--inverse-surface);
183
+ background: var(--inverse-surface); color: var(--inverse-text);
184
+ border-radius: var(--radius-md); padding: 0 var(--space-8); height: 32px;
190
185
  cursor: pointer; user-select: none; transition: var(--transition-control);
191
186
  }
192
- .cdr-btn:hover { background: var(--primary-hover); }
193
- .cdr-btn:active { background: var(--primary-press); transform: translateY(0.5px); }
187
+ .cdr-btn:hover { background: var(--inverse-surface-hover); border-color: var(--inverse-surface-hover); }
188
+ .cdr-btn:active { transform: scale(0.97); }
194
189
  .cdr-btn[disabled] { opacity: 0.45; pointer-events: none; }
195
- [data-theme="dark"] .cdr-btn:hover { box-shadow: var(--glow); }
196
190
 
197
- .cdr-btn--secondary { background: var(--surface); color: var(--text); border-color: var(--border-strong); }
198
- .cdr-btn--secondary:hover { background: var(--surface-2); border-color: var(--text-subtle); }
199
- [data-theme="dark"] .cdr-btn--secondary:hover { box-shadow: none; }
191
+ .cdr-btn--secondary { background: var(--surface-card); color: var(--text-primary); border-color: var(--border-default); }
192
+ .cdr-btn--secondary:hover { background: var(--surface-hover); border-color: var(--border-default); }
200
193
 
201
- .cdr-btn--sm { height: 30px; font-size: var(--text-xs); padding: 0 var(--space-3); gap: var(--space-1_5); }
202
- .cdr-btn--lg { height: 46px; font-size: var(--text-base); padding: 0 var(--space-6); }
194
+ .cdr-btn--sm { height: 28px; font-size: var(--text-xs); padding: 0 var(--space-6); gap: var(--space-2); }
195
+ .cdr-btn--lg { height: 38px; font-size: var(--text-base); padding: 0 var(--space-10); }
203
196
 
204
197
  .cdr-iconbtn {
205
198
  display: inline-flex; align-items: center; justify-content: center;
206
- width: 38px; height: 38px; border-radius: var(--radius-control);
207
- border: 1px solid transparent; background: transparent; color: var(--text-muted);
199
+ width: 28px; height: 28px; border-radius: var(--radius-md);
200
+ border: none; background: transparent; color: var(--text-secondary);
208
201
  cursor: pointer; transition: var(--transition-control); flex-shrink: 0;
209
202
  }
210
- .cdr-iconbtn:hover { background: var(--surface-2); color: var(--text); }
211
- .cdr-iconbtn--solid { background: var(--surface); border-color: var(--border-strong); }
212
- [data-theme="dark"] .cdr-iconbtn--solid:hover { box-shadow: var(--glow); }
213
- .cdr-iconbtn svg { width: 18px; height: 18px; }
203
+ .cdr-iconbtn:hover { background: var(--surface-hover); color: var(--text-primary); }
204
+ .cdr-iconbtn--solid { background: var(--surface-card); border: 1px solid var(--border-default); }
205
+ .cdr-iconbtn--solid:hover { background: var(--surface-hover); }
206
+ .cdr-iconbtn svg { display: block; width: 16px; height: 16px; }
207
+ .cdr-iconbtn > span { display: inline-flex; }
214
208
 
215
209
  .gp-comment-btn {
216
210
  display: inline-flex; align-items: center; justify-content: center;
217
- width: 30px; height: 30px; flex-shrink: 0; border: none; border-radius: var(--radius-sm);
218
- background: transparent; color: var(--text-subtle); cursor: pointer;
211
+ width: 28px; height: 28px; flex-shrink: 0; border: none; border-radius: var(--radius-md);
212
+ background: transparent; color: var(--text-tertiary); cursor: pointer;
219
213
  opacity: 0.3;
220
214
  transition: opacity 120ms ease, background 120ms ease, color 120ms ease;
221
215
  }
@@ -223,58 +217,53 @@ h1, h2, h3 {
223
217
  .gp-comment-btn:focus-visible,
224
218
  .gp-comment-btn--open,
225
219
  .gp-comment-btn--active { opacity: 1; }
226
- .gp-comment-btn--open { background: var(--surface-3); }
227
- .gp-comment-btn--active { color: var(--accent-text); }
228
- .gp-comment-btn svg { width: 14px; height: 14px; }
229
-
230
- /* ---------------------------------------------------------- CARD */
231
- .cdr-card {
232
- background: var(--surface); border: 1px solid var(--border);
233
- border-radius: var(--radius-card); box-shadow: var(--shadow-sm); overflow: clip;
234
- }
220
+ .gp-comment-btn:hover { background: var(--surface-hover); color: var(--text-primary); }
221
+ .gp-comment-btn--open { background: var(--surface-active); }
222
+ .gp-comment-btn--active { color: var(--accent); }
223
+ .gp-comment-btn svg { display: block; width: 14px; height: 14px; }
235
224
 
236
225
  /* ---------------------------------------------------------- BADGE */
237
226
  .cdr-badge {
238
- display: inline-flex; align-items: center; gap: var(--space-1);
239
- font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: var(--fw-medium);
240
- letter-spacing: 0.02em; text-transform: uppercase; padding: 3px var(--space-2);
241
- border-radius: var(--radius-xs); background: var(--surface-3); color: var(--text-muted);
227
+ display: inline-flex; align-items: center; gap: var(--space-2);
228
+ font-family: var(--font-sans); font-size: var(--text-xs); font-weight: var(--weight-medium);
229
+ padding: 3px var(--space-5); border-radius: var(--radius-pill);
230
+ background: var(--surface-hover); color: var(--text-secondary);
242
231
  line-height: 1.4; white-space: nowrap;
243
232
  }
244
- .cdr-badge--accent { background: var(--accent-soft); color: var(--accent-text); }
245
- .cdr-badge--success { background: var(--success-soft); color: var(--success-text); }
246
- .cdr-badge--warning { background: var(--warning-soft); color: var(--warning-text); }
233
+ .cdr-badge--accent { background: var(--accent-tint); color: var(--accent); }
234
+ .cdr-badge--success { background: var(--status-success-tint); color: var(--status-success); }
235
+ .cdr-badge--warning { background: var(--status-warning-tint); color: var(--status-warning); }
247
236
  .cdr-badge--dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
248
237
 
249
238
  /* ---------------------------------------------------------- AVATAR */
250
239
  .cdr-avatar {
251
240
  display: inline-flex; align-items: center; justify-content: center;
252
- border-radius: 50%; background: var(--surface-3); color: var(--text);
253
- font-family: var(--font-display); font-weight: var(--fw-semibold);
241
+ border-radius: 50%; color: #fff;
242
+ font-family: var(--font-sans); font-weight: var(--weight-semibold);
254
243
  overflow: hidden; flex-shrink: 0; user-select: none;
255
244
  }
256
- .cdr-avatar--sm { width: 26px; height: 26px; font-size: 11px; }
245
+ .cdr-avatar--sm { width: 24px; height: 24px; font-size: 11px; }
257
246
 
258
247
  /* ---------------------------------------------------------- CHECKBOX / RADIO */
259
248
  .cdr-choice {
260
- display: inline-flex; align-items: center; gap: var(--space-2_5); cursor: pointer;
261
- font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text);
249
+ display: inline-flex; align-items: center; gap: var(--space-4); cursor: pointer;
250
+ font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-primary);
262
251
  }
263
252
  .cdr-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
264
253
  .cdr-box {
265
- width: 18px; height: 18px; flex-shrink: 0; border: 1.5px solid var(--border-strong);
266
- background: var(--surface); border-radius: var(--radius-xs);
254
+ width: 16px; height: 16px; flex-shrink: 0; border: 1px solid var(--border-strong);
255
+ background: var(--surface-card); border-radius: var(--radius-sm);
267
256
  display: inline-flex; align-items: center; justify-content: center;
268
- color: var(--text-on-accent); transition: var(--transition-control);
257
+ color: var(--inverse-text); transition: var(--transition-control);
269
258
  }
270
259
  .cdr-box--radio { border-radius: 50%; }
271
- .cdr-choice:hover .cdr-box { border-color: var(--text-subtle); }
272
- .cdr-choice input:focus-visible + .cdr-box { box-shadow: var(--focus-ring); }
273
- .cdr-choice input:checked + .cdr-box { background: var(--primary); border-color: var(--primary); }
274
- .cdr-box svg { width: 13px; height: 13px; opacity: 0; transform: scale(0.6); transition: var(--transition-control); }
260
+ .cdr-choice:hover .cdr-box { border-color: var(--text-tertiary); }
261
+ .cdr-choice input:focus-visible + .cdr-box { box-shadow: var(--shadow-focus-ring); }
262
+ .cdr-choice input:checked + .cdr-box { background: var(--inverse-surface); border-color: var(--inverse-surface); }
263
+ .cdr-box svg { width: 12px; height: 12px; opacity: 0; transform: scale(0.6); transition: var(--transition-control); }
275
264
  .cdr-choice input:checked + .cdr-box svg { opacity: 1; transform: scale(1); }
276
265
  .cdr-box--radio::after {
277
- content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--text-on-accent);
266
+ content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--inverse-text);
278
267
  opacity: 0; transform: scale(0.5); transition: var(--transition-control);
279
268
  }
280
269
  .cdr-choice input:checked + .cdr-box--radio::after { opacity: 1; transform: scale(1); }
@@ -283,166 +272,195 @@ h1, h2, h3 {
283
272
 
284
273
  /* ---------------------------------------------------------- TEXTAREA */
285
274
  .cdr-textarea {
286
- font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text);
287
- background: var(--surface); border: 1px solid var(--border-strong);
288
- border-radius: var(--radius-control); padding: var(--space-3); width: 100%;
289
- line-height: var(--leading-normal); resize: vertical; min-height: 80px;
275
+ font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-primary);
276
+ background: var(--surface-card); border: 1px solid var(--border-default);
277
+ border-radius: var(--radius-md); padding: var(--space-6); width: 100%;
278
+ line-height: var(--leading-normal); resize: vertical; min-height: 76px;
290
279
  transition: var(--transition-control);
291
280
  }
292
- .cdr-textarea::placeholder { color: var(--text-subtle); }
293
- .cdr-textarea:hover { border-color: var(--text-subtle); }
294
- .cdr-textarea:focus { outline: none; border-color: var(--primary); box-shadow: var(--focus-ring); }
281
+ .cdr-textarea::placeholder { color: var(--text-tertiary); }
282
+ .cdr-textarea:hover { border-color: var(--border-strong); }
283
+ .cdr-textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--shadow-focus-ring); }
295
284
 
296
285
  /* ---------------------------------------------------------- BANNER */
297
286
  .cdr-banner {
298
- display: flex; align-items: flex-start; gap: var(--space-3);
299
- padding: var(--space-3) var(--space-4); border: 1px solid var(--border);
300
- border-radius: var(--radius-md); background: var(--surface); color: var(--text);
301
- font-size: var(--text-sm); border-left-width: 3px;
287
+ display: flex; align-items: flex-start; gap: var(--space-6);
288
+ padding: var(--space-6) var(--space-8); border-radius: var(--radius-md);
289
+ background: var(--surface-hover); color: var(--text-primary); font-size: var(--text-sm);
302
290
  }
303
291
  .cdr-banner svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }
304
- .cdr-banner--success { border-left-color: var(--success); background: var(--success-soft); }
305
- .cdr-banner--success svg { color: var(--success); }
306
- .cdr-banner-title { font-weight: var(--fw-semibold); }
307
- .cdr-banner-body { color: var(--text-muted); margin-top: 2px; }
292
+ .cdr-banner--success { background: var(--status-success-tint); }
293
+ .cdr-banner--success svg { color: var(--status-success); }
294
+ .cdr-banner-title { font-weight: var(--weight-semibold); }
295
+ .cdr-banner-body { color: var(--text-secondary); margin-top: 2px; }
308
296
 
309
297
  /* ============================================================
310
298
  gigaplan layout
311
299
  ============================================================ */
312
300
 
313
- .gp-app { min-height: 100vh; background: var(--bg); color: var(--text); font-family: var(--font-sans); }
301
+ .gp-app { min-height: 100vh; background: var(--surface-page); color: var(--text-primary); font-family: var(--font-sans); }
314
302
 
315
303
  /* ---- top bar ---- */
316
304
  .gp-topbar {
317
- position: sticky; top: 0; z-index: 20; background: var(--surface); border-bottom: 1px solid var(--border);
305
+ position: sticky; top: 0; z-index: 20; background: var(--surface-page); border-bottom: 1px solid var(--border-subtle);
318
306
  }
319
307
  .gp-topbar-inner {
320
- max-width: 1320px; margin: 0 auto; padding: 16px 32px;
321
- display: flex; flex-direction: column; gap: 10px;
308
+ max-width: 1200px; margin: 0 auto; padding: var(--space-8) var(--space-12);
309
+ display: flex; flex-direction: column; gap: var(--space-3);
322
310
  }
323
- .gp-topbar-row { display: flex; align-items: center; gap: 14px; }
324
- .gp-topbar-heading { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; }
325
- .gp-topbar-brand {
326
- font-family: var(--font-mono); font-weight: 600; font-size: 14px; color: var(--primary); white-space: nowrap;
311
+ .gp-topbar-row { display: flex; align-items: center; gap: var(--space-7); }
312
+ .gp-topbar-heading { display: flex; align-items: center; gap: var(--space-5); flex: 1; min-width: 0; }
313
+ .gp-topbar-mark {
314
+ width: 22px; height: 22px; flex-shrink: 0; border-radius: 5px;
315
+ background: var(--inverse-surface); color: var(--inverse-text);
316
+ display: flex; align-items: center; justify-content: center;
327
317
  }
318
+ .gp-topbar-mark svg { display: block; width: 14px; height: 14px; }
328
319
  .gp-topbar-title {
329
- font-family: var(--font-mono); font-weight: 600; font-size: 14px; color: var(--text);
330
- min-width: 0; line-height: 1.15;
320
+ font-family: var(--font-sans); font-weight: var(--weight-semibold); font-size: var(--text-md); color: var(--text-primary);
321
+ min-width: 0; line-height: 1.2;
331
322
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
332
323
  }
333
324
  .gp-topbar-meta {
334
- display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--text-muted); flex-wrap: wrap;
325
+ display: flex; align-items: center; gap: var(--space-5); font-size: var(--text-sm); color: var(--text-tertiary); flex-wrap: wrap;
335
326
  }
336
- .gp-topbar-meta b { color: var(--text); font-weight: 600; }
337
- .gp-topbar-meta .gp-dot { color: var(--border-strong); }
338
- .gp-topbar-meta .gp-mono { font-family: var(--font-mono); font-size: 12px; }
327
+ .gp-topbar-meta b { color: var(--text-primary); font-weight: var(--weight-medium); }
328
+ .gp-topbar-meta .gp-dot { color: var(--text-disabled); }
339
329
 
340
330
  /* ---- layout grid ---- */
341
331
  .gp-body {
342
- max-width: 1320px; margin: 0 auto; padding: 28px 32px 0;
343
- display: grid; grid-template-columns: 1fr 320px; gap: 28px; align-items: start;
332
+ max-width: 1200px; margin: 0 auto; padding: var(--space-12) var(--space-12) 0;
333
+ display: grid; grid-template-columns: 1fr 300px; gap: var(--space-12); align-items: start;
344
334
  }
345
- .gp-main-col { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
335
+ .gp-main-col { display: flex; flex-direction: column; gap: var(--space-10); min-width: 0; }
346
336
 
347
- /* ---- section card ---- */
348
- .gp-section { border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--surface); overflow: hidden; box-shadow: var(--shadow-sm); }
337
+ /* ---- section heading (flat — a heading with a hover-reveal fold arrow,
338
+ not a boxed card; sections are just `##` headings, not a formal division) ---- */
349
339
  .gp-section-head {
350
- display: flex; align-items: center; gap: 10px; padding: 14px 20px;
351
- background: var(--surface-2); border-bottom: 1px solid var(--border);
340
+ display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3) 0;
341
+ }
342
+ .gp-section-collapse {
343
+ background: none; border: none; cursor: pointer; color: var(--text-tertiary);
344
+ display: flex; align-items: center; padding: 0; flex-shrink: 0;
345
+ opacity: 0.35; transition: opacity var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
352
346
  }
353
- .gp-section-collapse { background: none; border: none; cursor: pointer; color: var(--text-subtle); display: flex; padding: 0; flex-shrink: 0; }
354
- .gp-section-collapse svg { width: 16px; height: 16px; }
355
- .gp-section-num { font-family: var(--font-mono); font-size: 12px; color: var(--text-subtle); flex-shrink: 0; }
356
- .gp-section-title { font-family: var(--font-display); font-weight: 700; font-size: 15px; }
357
- .gp-section-spacer { margin-left: auto; }
358
- .gp-section-body { padding: 6px 20px 20px; }
347
+ .gp-section-head:hover .gp-section-collapse,
348
+ .gp-section-head:hover .gp-comment-btn { opacity: 1; }
349
+ .gp-section-collapse:hover { color: var(--text-primary); }
350
+ .gp-section-collapse svg { display: block; width: 15px; height: 15px; }
351
+ .gp-section-title { font-family: var(--font-sans); font-weight: var(--weight-semibold); font-size: var(--heading-h2); letter-spacing: var(--tracking-tight); color: var(--text-primary); line-height: var(--leading-snug); }
352
+ .gp-section-body { padding-top: var(--space-4); }
359
353
 
360
354
  /* ---- commentable rows ---- */
361
- .gp-commentable { position: relative; padding: 8px 0; scroll-margin-top: 96px; }
355
+ .gp-commentable { position: relative; padding: var(--space-4) 0; scroll-margin-top: 96px; }
362
356
  .gp-section { scroll-margin-top: 96px; }
363
- .gp-commentable-row { display: flex; align-items: flex-start; gap: 12px; }
357
+ .gp-commentable-row { display: flex; align-items: flex-start; gap: var(--space-6); }
364
358
  .gp-commentable-content { flex: 1; min-width: 0; }
365
- .gp-commentable-content p { margin: 0; font-size: 14.5px; line-height: 1.65; color: var(--text); }
366
-
367
- .gp-item-ol { display: flex; gap: 10px; font-size: 14.5px; line-height: 1.7; color: var(--text); }
368
- .gp-item-ol .gp-ordinal { font-family: var(--font-mono); color: var(--text-subtle); flex-shrink: 0; }
369
- .gp-item-ul { display: flex; gap: 10px; font-size: 14.5px; line-height: 1.7; color: var(--text-muted); }
370
- .gp-item-ul .gp-bullet { color: var(--text-subtle); flex-shrink: 0; }
371
- .gp-item-checklist { display: flex; align-items: flex-start; gap: 10px; font-size: 14.5px; line-height: 1.5; color: var(--text-muted); }
372
-
373
- .gp-commentable-content pre {
374
- margin: 0; background: var(--surface-inset); border: 1px solid var(--border);
375
- border-radius: var(--radius-md); padding: 16px 18px; overflow-x: auto;
376
- font-family: var(--font-mono); font-size: 13px; line-height: 1.6; color: var(--text); white-space: pre;
377
- }
378
- .gp-commentable-content table { border-collapse: collapse; display: block; overflow-x: auto; width: max-content; max-width: 100%; }
379
- .gp-commentable-content th, .gp-commentable-content td { border: 1px solid var(--border); padding: 6px 12px; font-size: 13.5px; }
380
- .gp-commentable-content th { font-family: var(--font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-subtle); }
359
+ .gp-commentable-content p { margin: 0; font-size: var(--text-base); line-height: var(--leading-relaxed); color: var(--text-primary); }
360
+
361
+ .gp-commentable-content h1, .gp-commentable-content h2, .gp-commentable-content h3,
362
+ .gp-commentable-content h4, .gp-commentable-content h5, .gp-commentable-content h6 {
363
+ font-family: var(--font-sans); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-tight);
364
+ color: var(--text-primary); margin: 0; line-height: var(--leading-snug);
365
+ }
366
+ .gp-commentable-content h1 { font-size: var(--heading-h1); }
367
+ .gp-commentable-content h2 { font-size: var(--heading-h2); }
368
+ .gp-commentable-content h3 { font-size: var(--heading-h3); }
369
+ .gp-commentable-content h4, .gp-commentable-content h5, .gp-commentable-content h6 { font-size: var(--text-md); }
370
+
371
+ .gp-commentable-content code {
372
+ font-family: var(--font-mono); background: var(--surface-code);
373
+ padding: 2px 5px; border-radius: var(--radius-sm); font-size: 0.85em;
374
+ }
375
+ .gp-commentable-content a { color: var(--accent); }
376
+
377
+ .gp-item-ol { display: flex; gap: var(--space-6); font-size: var(--text-base); line-height: var(--leading-relaxed); color: var(--text-primary); }
378
+ .gp-item-ol .gp-ordinal { font-family: var(--font-sans); color: var(--text-tertiary); flex-shrink: 0; width: 18px; text-align: right; }
379
+ .gp-item-ul { display: flex; gap: var(--space-6); font-size: var(--text-base); line-height: var(--leading-relaxed); color: var(--text-primary); }
380
+ .gp-item-ul .gp-bullet { color: var(--text-tertiary); flex-shrink: 0; width: 18px; text-align: center; }
381
+ .gp-item-checklist { display: flex; align-items: flex-start; gap: var(--space-6); font-size: var(--text-base); line-height: var(--leading-normal); color: var(--text-primary); padding-top: 1px; }
382
+
383
+ /* ---- code fence ---- */
384
+ .gp-code { background: var(--surface-code); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); overflow: hidden; }
385
+ .gp-code-head {
386
+ display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
387
+ padding: var(--space-3) var(--space-6); font-family: var(--font-mono); font-size: 11px;
388
+ color: var(--text-tertiary); border-bottom: 1px solid var(--border-subtle);
389
+ }
390
+ .gp-code-copy { background: none; border: none; padding: 0; font-family: var(--font-sans); font-size: 11px; color: var(--text-tertiary); cursor: pointer; }
391
+ .gp-code-copy:hover { color: var(--text-primary); }
392
+ .gp-commentable-content .gp-code pre {
393
+ margin: 0; background: none; border: none; border-radius: 0;
394
+ padding: var(--space-7) var(--space-8); overflow-x: auto;
395
+ font-family: var(--font-mono); font-size: 13px; line-height: 1.6; color: var(--text-primary); white-space: pre;
396
+ }
397
+ .gp-commentable-content .gp-code pre code { background: none; padding: 0; border-radius: 0; font-size: 1em; }
398
+
399
+ .gp-commentable-content table { border-collapse: collapse; display: block; overflow-x: auto; width: max-content; max-width: 100%; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); }
400
+ .gp-commentable-content th, .gp-commentable-content td { border: 1px solid var(--border-subtle); padding: var(--space-4) var(--space-6); font-size: var(--text-sm); }
401
+ .gp-commentable-content th { font-family: var(--font-sans); font-size: 12px; font-weight: var(--weight-semibold); color: var(--text-tertiary); background: var(--surface-sidebar); }
381
402
  .gp-commentable-content blockquote {
382
- margin: 0; padding: 8px 16px; border-left: 3px solid var(--border-strong);
383
- color: var(--text-muted); font-size: 14.5px; line-height: 1.6;
403
+ margin: 0; padding-left: var(--space-7); border-left: 3px solid var(--gray-950);
404
+ color: var(--text-primary); font-size: var(--heading-h3); line-height: var(--leading-snug); font-weight: var(--weight-medium);
384
405
  }
385
- .gp-hr { border: 0; border-top: 1px solid var(--border); margin: 4px 0; }
406
+ [data-theme="dark"] .gp-commentable-content blockquote { border-left-color: var(--gray-0, #fff); }
407
+ .gp-hr { border: 0; border-top: 1px solid var(--border-subtle); margin: var(--space-2) 0; }
386
408
 
387
409
  /* ---- comment thread + composer ---- */
388
410
  .gp-thread-comment {
389
- margin: 10px 0; padding: 12px 14px; background: var(--surface-2);
390
- border-radius: var(--radius-md); border-left: 3px solid var(--border-strong);
411
+ margin: var(--space-5) 0; padding: var(--space-6) var(--space-7); background: var(--surface-hover);
412
+ border-radius: var(--radius-md);
391
413
  }
392
- .gp-thread-comment-head { display: flex; align-items: center; gap: 8px; }
393
- .gp-thread-comment-author { font-size: 13px; font-weight: 600; }
394
- .gp-thread-comment-time { font-family: var(--font-mono); font-size: 11px; color: var(--text-subtle); margin-left: auto; }
395
- .gp-thread-comment-body { font-size: 13.5px; line-height: 1.55; color: var(--text-muted); margin-top: 6px; white-space: pre-wrap; }
414
+ .gp-thread-comment-head { display: flex; align-items: center; gap: var(--space-4); }
415
+ .gp-thread-comment-author { font-size: var(--text-sm); font-weight: var(--weight-semibold); }
416
+ .gp-thread-comment-time { font-family: var(--font-sans); font-size: 11px; color: var(--text-tertiary); margin-left: auto; }
417
+ .gp-thread-comment-body { font-size: var(--text-sm); line-height: var(--leading-normal); color: var(--text-primary); margin-top: var(--space-3); white-space: pre-wrap; }
396
418
  .gp-stale-banner {
397
- font-size: 12px; color: var(--warning-text); background: var(--warning-soft);
398
- border: 1px solid var(--warning); border-radius: var(--radius-sm); padding: 4px 8px; margin: 6px 0;
419
+ font-size: var(--text-xs); color: var(--status-warning); background: var(--status-warning-tint);
420
+ border-radius: var(--radius-sm); padding: var(--space-3) var(--space-5); margin: var(--space-3) 0;
399
421
  }
400
422
 
401
- .gp-composer { margin: 10px 0; padding: 14px; border: 1px solid var(--border-strong); border-radius: var(--radius-md); background: var(--surface); }
402
- .gp-composer-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
423
+ .gp-composer { margin: var(--space-5) 0; padding: var(--space-7); border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--surface-card); }
424
+ .gp-composer-actions { display: flex; justify-content: flex-end; gap: var(--space-4); margin-top: var(--space-6); }
403
425
 
404
426
  /* ---- orphaned section ---- */
405
- .gp-orphaned-section { margin: 8px 0 0; padding-top: 16px; border-top: 1px dashed var(--border); }
406
- .gp-orphaned-section h2 { font-size: 14px; color: var(--text-muted); font-family: var(--font-sans); font-weight: 600; }
407
- .gp-orphaned-meta { font-size: 12px; color: var(--text-subtle); margin-bottom: 4px; }
408
-
409
- /* ---- sidebar ---- */
410
- .gp-sidebar { position: sticky; top: 110px; display: flex; flex-direction: column; gap: 16px; }
411
- .gp-coverage-card { padding: 18px 20px; }
412
- .gp-coverage-numbers { display: flex; align-items: baseline; gap: 8px; margin-top: 10px; }
413
- .gp-coverage-count { font-family: var(--font-display); font-weight: 800; font-size: 30px; letter-spacing: -0.03em; }
414
- .gp-coverage-total { font-size: 13px; color: var(--text-muted); }
415
- .gp-coverage-bar { height: 6px; background: var(--surface-3); border-radius: 999px; overflow: hidden; margin-top: 12px; }
416
- .gp-coverage-bar-fill { height: 100%; background: var(--primary); border-radius: inherit; transition: width var(--dur-base) ease; }
417
- .gp-coverage-footer { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 12.5px; color: var(--text-muted); }
418
-
419
- .gp-comments-card-head { padding: 14px 20px; border-bottom: 1px solid var(--border); font-family: var(--font-display); font-weight: 700; font-size: 14px; }
420
- .gp-comment-row { cursor: pointer; padding: 14px 20px; border-bottom: 1px solid var(--border); transition: background var(--dur-fast) ease; }
421
- .gp-comment-row:last-child { border-bottom: none; }
422
- .gp-comment-row:hover { background: var(--surface-2); }
423
- .gp-comment-row-head { display: flex; align-items: center; gap: 8px; }
424
- .gp-comment-row-author { font-size: 12.5px; font-weight: 600; }
425
- .gp-comment-row-time { font-family: var(--font-mono); font-size: 11px; color: var(--text-subtle); margin-left: auto; }
427
+ .gp-orphaned-section { margin: var(--space-4) 0 0; padding-top: var(--space-8); border-top: 1px dashed var(--border-default); }
428
+ .gp-orphaned-section h2 { font-size: var(--text-sm); color: var(--text-secondary); font-family: var(--font-sans); font-weight: var(--weight-semibold); }
429
+ .gp-orphaned-meta { font-size: var(--text-xs); color: var(--text-tertiary); margin-bottom: var(--space-2); }
430
+
431
+ /* ---- sidebar (flat comments list, one hairline divider from the document) ---- */
432
+ .gp-sidebar {
433
+ position: sticky; top: 104px; display: flex; flex-direction: column;
434
+ border-left: 1px solid var(--border-subtle); padding-left: var(--space-10);
435
+ }
436
+ .gp-comments-card-head { font-family: var(--font-sans); font-weight: var(--weight-semibold); font-size: var(--text-sm); color: var(--text-primary); padding-bottom: var(--space-4); }
437
+ .gp-comment-row {
438
+ cursor: pointer; padding: var(--space-4) var(--space-4); margin: 0 calc(-1 * var(--space-4));
439
+ border-radius: var(--radius-md); transition: background var(--duration-fast) ease;
440
+ }
441
+ .gp-comment-row:hover { background: var(--surface-hover); }
442
+ .gp-comment-row-head { display: flex; align-items: center; gap: var(--space-4); }
443
+ .gp-comment-row-author { font-size: var(--text-xs); font-weight: var(--weight-semibold); }
444
+ .gp-comment-row-time { font-family: var(--font-sans); font-size: 11px; color: var(--text-tertiary); margin-left: auto; }
426
445
  .gp-comment-row-section {
427
- font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase;
428
- color: var(--accent-text); margin-top: 8px;
446
+ font-family: var(--font-sans); font-size: var(--text-xs); font-weight: var(--weight-medium);
447
+ color: var(--accent); margin-top: var(--space-4);
429
448
  }
430
449
  .gp-comment-row-text {
431
- font-size: 12.5px; line-height: 1.5; color: var(--text-muted); margin-top: 4px;
450
+ font-size: var(--text-xs); line-height: 1.5; color: var(--text-secondary); margin-top: var(--space-2);
432
451
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
433
452
  }
434
- .gp-empty-comments { padding: 20px; font-size: 13px; color: var(--text-subtle); line-height: 1.5; }
453
+ .gp-empty-comments { padding: var(--space-4) 0; font-size: var(--text-sm); color: var(--text-tertiary); line-height: 1.5; }
435
454
 
436
- /* ---- finish review panel ---- */
437
- .gp-finish-panel { max-width: 1320px; margin: 0 auto; padding: 28px 32px 56px; }
438
- .gp-finish-head { padding: 20px 24px; border-bottom: 1px solid var(--border); }
439
- .gp-finish-title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: 700; margin-top: 6px; }
440
- .gp-finish-body { padding: 22px 24px; display: flex; flex-direction: column; gap: 18px; }
441
- .gp-verdict-label { font-size: 13px; font-weight: 600; margin-bottom: 12px; }
442
- .gp-verdict-options { display: flex; gap: 24px; flex-wrap: wrap; }
443
- .gp-verdict-hint { font-size: 12px; color: var(--text-subtle); margin-top: 8px; }
455
+ /* ---- finish review panel (flat, one top divider off the sections above) ---- */
456
+ .gp-finish-panel { max-width: 1200px; margin: var(--space-8) auto 0; padding: var(--space-12) var(--space-12) 56px; border-top: 1px solid var(--border-subtle); }
457
+ .gp-finish-title { font-family: var(--font-sans); font-size: var(--heading-h2); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-tight); padding-top: var(--space-12); }
458
+ .gp-finish-body { padding-top: var(--space-8); display: flex; flex-direction: column; gap: var(--space-8); }
459
+ .gp-verdict-label { font-size: var(--text-sm); font-weight: var(--weight-semibold); margin-bottom: var(--space-6); }
460
+ .gp-verdict-options { display: flex; gap: var(--space-12); flex-wrap: wrap; }
461
+ .gp-verdict-hint { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: var(--space-4); }
444
462
  .gp-finish-actions { display: flex; justify-content: flex-end; }
445
- .gp-submitted-quote { font-size: 13.5px; line-height: 1.6; color: var(--text-muted); padding: 0 4px; }
463
+ .gp-submitted-quote { font-size: var(--text-sm); line-height: var(--leading-relaxed); color: var(--text-secondary); padding: 0 var(--space-2); }
446
464
 
447
465
  @media (max-width: 900px) {
448
466
  .gp-body { grid-template-columns: 1fr; }
package/public/chrome.js CHANGED
@@ -4,6 +4,7 @@ const ICON_PATHS = {
4
4
  "chevron-down": '<path d="m6 9 6 6 6-6"/>',
5
5
  "chevron-right": '<path d="m9 18 6-6-6-6"/>',
6
6
  "message-square": '<path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/>',
7
+ "message-square-check": '<path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.7.7 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/><path d="m9 11 2 2 4-4"/>',
7
8
  sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
8
9
  moon: '<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>',
9
10
  check: '<path d="M20 6 9 17l-5-5"/>',
@@ -18,18 +19,15 @@ function buildSections(blocks) {
18
19
  let pageTitle = "";
19
20
  const sections = [];
20
21
  let current = null;
21
- let counter = 0;
22
22
  for (const block of blocks) {
23
23
  if (block.headingLevel === 1 && !pageTitle) {
24
24
  pageTitle = block.headingBreadcrumb[block.headingBreadcrumb.length - 1] ?? block.excerpt;
25
25
  continue;
26
26
  }
27
27
  if (block.headingLevel === 2) {
28
- counter++;
29
28
  current = {
30
29
  id: block.id,
31
30
  headingBlockId: block.id,
32
- num: String(counter).padStart(2, "0"),
33
31
  title: block.headingBreadcrumb[block.headingBreadcrumb.length - 1] ?? block.excerpt,
34
32
  items: [],
35
33
  };
@@ -37,11 +35,9 @@ function buildSections(blocks) {
37
35
  continue;
38
36
  }
39
37
  if (!current) {
40
- counter++;
41
38
  current = {
42
39
  id: `intro-${block.id}`,
43
40
  headingBlockId: null,
44
- num: String(counter).padStart(2, "0"),
45
41
  title: "Overview",
46
42
  items: [],
47
43
  };
@@ -69,6 +65,26 @@ function initials(name) {
69
65
  .map((w) => w[0]?.toUpperCase() ?? "")
70
66
  .join("");
71
67
  }
68
+ // Ported from the Belief design system's Avatar.jsx: a deterministic
69
+ // per-name color from a fixed 6-color palette, so "You" always gets the
70
+ // same circle color across a session instead of a flat neutral fill.
71
+ const AVATAR_COLORS = [
72
+ "var(--avatar-1)",
73
+ "var(--avatar-2)",
74
+ "var(--avatar-3)",
75
+ "var(--avatar-4)",
76
+ "var(--avatar-5)",
77
+ "var(--avatar-6)",
78
+ ];
79
+ function avatarColorFor(name) {
80
+ let hash = 0;
81
+ for (let i = 0; i < name.length; i++)
82
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
83
+ return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
84
+ }
85
+ function avatarHtml(name, size = "sm") {
86
+ return `<span class="cdr-avatar cdr-avatar--${size}" style="background:${avatarColorFor(name)}">${initials(name)}</span>`;
87
+ }
72
88
  /* ---------------------------------------------------------- state */
73
89
  const dataEl = document.getElementById("gigaplan-data");
74
90
  const initial = JSON.parse(dataEl?.textContent ?? "{}");
@@ -81,7 +97,6 @@ const state = {
81
97
  drafts: new Map(),
82
98
  openComposer: null,
83
99
  collapsed: new Set(),
84
- reviewed: new Set(),
85
100
  verdict: null,
86
101
  globalComment: "",
87
102
  submitted: false,
@@ -111,7 +126,7 @@ function commentThreadHtml(blockId) {
111
126
  parts.push(`
112
127
  <div class="gp-thread-comment" data-comment-for="${blockId}">
113
128
  <div class="gp-thread-comment-head">
114
- <span class="cdr-avatar cdr-avatar--sm">${initials("You")}</span>
129
+ ${avatarHtml("You")}
115
130
  <span class="gp-thread-comment-author">You</span>
116
131
  <span class="gp-thread-comment-time">${relativeTime(comment.createdAt)}</span>
117
132
  </div>
@@ -153,6 +168,18 @@ function itemContentHtml(block) {
153
168
  }
154
169
  return `<div class="gp-item-ul"><span class="gp-bullet">&bull;</span><span>${block.html}</span></div>`;
155
170
  }
171
+ if (block.type === "fence") {
172
+ const langMatch = /language-([\w-]+)/.exec(block.html);
173
+ const lang = langMatch ? langMatch[1] : "text";
174
+ return `
175
+ <div class="gp-code" data-fence-for="${block.id}">
176
+ <div class="gp-code-head">
177
+ <span>${escapeText(lang)}</span>
178
+ <button type="button" class="gp-code-copy" data-action="copy-code" data-block-id="${block.id}">Copy</button>
179
+ </div>
180
+ ${block.html}
181
+ </div>`;
182
+ }
156
183
  return block.html;
157
184
  }
158
185
  function buildCommentableRow(block) {
@@ -194,7 +221,6 @@ function buildItemsHtml(items) {
194
221
  /* ---------------------------------------------------------- sections */
195
222
  function buildSectionHtml(section) {
196
223
  const collapsed = state.collapsed.has(section.id);
197
- const reviewed = state.reviewed.has(section.id);
198
224
  const commentTotal = (section.headingBlockId && state.pending.has(section.headingBlockId) ? 1 : 0) +
199
225
  section.items.filter((it) => state.pending.has(it.id)).length;
200
226
  const headingCommentBtn = section.headingBlockId
@@ -206,16 +232,9 @@ function buildSectionHtml(section) {
206
232
  <button type="button" class="gp-section-collapse" data-action="toggle-collapse" data-section-id="${section.id}" aria-label="Toggle section">
207
233
  ${iconSvg(collapsed ? "chevron-right" : "chevron-down")}
208
234
  </button>
209
- <span class="gp-section-num">${section.num}</span>
210
235
  <span class="gp-section-title">${section.title}</span>
211
- ${headingCommentBtn}
212
- <span class="gp-section-spacer"></span>
213
236
  ${commentTotal > 0 ? `<span class="cdr-badge cdr-badge--accent">${commentTotal} comment${commentTotal === 1 ? "" : "s"}</span>` : ""}
214
- <label class="cdr-choice">
215
- <input type="checkbox" data-action="toggle-reviewed" data-section-id="${section.id}" ${reviewed ? "checked" : ""} />
216
- <span class="cdr-box">${iconSvg("check", 13)}</span>
217
- Reviewed
218
- </label>
237
+ ${headingCommentBtn}
219
238
  </div>
220
239
  <div class="gp-section-body" data-section-body="${section.id}" ${collapsed ? 'style="display:none"' : ""}>
221
240
  ${section.headingBlockId ? `<div data-thread-for="${section.headingBlockId}">${commentThreadHtml(section.headingBlockId)}</div>` : ""}
@@ -234,7 +253,7 @@ function buildTopBarHtml() {
234
253
  <div class="gp-topbar-inner">
235
254
  <div class="gp-topbar-row">
236
255
  <div class="gp-topbar-heading">
237
- <span class="gp-topbar-brand">/gigaplan</span>
256
+ <span class="gp-topbar-mark" aria-hidden="true">${iconSvg("message-square-check", 14)}</span>
238
257
  <h1 class="gp-topbar-title">${escapeText(currentPageTitle)}</h1>
239
258
  </div>
240
259
  <span class="cdr-badge cdr-badge--${status.tone} cdr-badge--dot">${status.label}</span>
@@ -243,7 +262,7 @@ function buildTopBarHtml() {
243
262
  </button>
244
263
  </div>
245
264
  <div class="gp-topbar-meta">
246
- <span>${escapeText(currentPlanFilename())} &middot; <span class="gp-mono">${currentSections.length} section${currentSections.length === 1 ? "" : "s"}</span> &middot; <span class="gp-mono">${commentCount} comment${commentCount === 1 ? "" : "s"}</span></span>
265
+ <span>${escapeText(currentPlanFilename())} &middot; ${currentSections.length} section${currentSections.length === 1 ? "" : "s"} &middot; ${commentCount} comment${commentCount === 1 ? "" : "s"}</span>
247
266
  </div>
248
267
  </div>
249
268
  </div>`;
@@ -278,17 +297,13 @@ function collectAllComments() {
278
297
  return entries.sort((a, b) => a.createdAt - b.createdAt);
279
298
  }
280
299
  function buildSidebarHtml() {
281
- const reviewedCount = currentSections.filter((s) => state.reviewed.has(s.id)).length;
282
- const total = currentSections.length;
283
- const pct = total > 0 ? Math.round((reviewedCount / total) * 100) : 0;
284
300
  const allComments = collectAllComments();
285
- const commentCount = state.pending.size + state.orphanedPending.size;
286
301
  const commentsHtml = allComments.length > 0
287
302
  ? allComments
288
303
  .map((c) => `
289
304
  <div class="gp-comment-row" data-action="jump-to-comment" data-block-id="${c.blockId}">
290
305
  <div class="gp-comment-row-head">
291
- <span class="cdr-avatar cdr-avatar--sm">${initials("You")}</span>
306
+ ${avatarHtml("You")}
292
307
  <span class="gp-comment-row-author">You</span>
293
308
  <span class="gp-comment-row-time">${relativeTime(c.createdAt)}</span>
294
309
  </div>
@@ -296,22 +311,11 @@ function buildSidebarHtml() {
296
311
  <div class="gp-comment-row-text"></div>
297
312
  </div>`)
298
313
  .join("")
299
- : '<div class="gp-empty-comments">Comments you leave on the plan will show up here, so you can see review coverage at a glance.</div>';
314
+ : '<div class="gp-empty-comments">Comments you leave on the plan will show up here.</div>';
300
315
  return `
301
316
  <aside class="gp-sidebar">
302
- <div class="cdr-card gp-coverage-card">
303
- <div class="ds-eyebrow">// review coverage</div>
304
- <div class="gp-coverage-numbers">
305
- <span class="gp-coverage-count">${reviewedCount}</span>
306
- <span class="gp-coverage-total">of ${total} sections reviewed</span>
307
- </div>
308
- <div class="gp-coverage-bar"><div class="gp-coverage-bar-fill" style="width:${pct}%"></div></div>
309
- <div class="gp-coverage-footer">${commentCount} comment${commentCount === 1 ? "" : "s"} across the plan</div>
310
- </div>
311
- <div class="cdr-card">
312
- <div class="gp-comments-card-head">All comments</div>
313
- <div>${commentsHtml}</div>
314
- </div>
317
+ <div class="gp-comments-card-head">Comments</div>
318
+ <div>${commentsHtml}</div>
315
319
  </aside>`;
316
320
  }
317
321
  /* ---------------------------------------------------------- finish panel */
@@ -325,50 +329,42 @@ function buildFinishPanelHtml() {
325
329
  const verdictLabel = verdict === "approve" ? approveLabel : "Request changes";
326
330
  return `
327
331
  <div id="gp-finish-panel" class="gp-finish-panel">
328
- <div class="cdr-card">
329
- <div class="gp-finish-head">
330
- <h2 class="gp-finish-title">Review submitted</h2>
331
- </div>
332
- <div class="gp-finish-body">
333
- <div class="cdr-banner cdr-banner--success">
334
- ${iconSvg("circle-check", 18)}
335
- <div>
336
- <div class="cdr-banner-title">Review submitted</div>
337
- <div class="cdr-banner-body">Verdict: ${verdictLabel}. Revise the plan and reopen this session to review again.</div>
338
- </div>
332
+ <h2 class="gp-finish-title">Review submitted</h2>
333
+ <div class="gp-finish-body">
334
+ <div class="cdr-banner cdr-banner--success">
335
+ ${iconSvg("circle-check", 18)}
336
+ <div>
337
+ <div class="cdr-banner-title">Review submitted</div>
338
+ <div class="cdr-banner-body">Verdict: ${verdictLabel}. Revise the plan and reopen this session to review again.</div>
339
339
  </div>
340
- ${state.globalComment.trim() ? `<div class="gp-submitted-quote">&ldquo;<span></span>&rdquo;</div>` : ""}
341
340
  </div>
341
+ ${state.globalComment.trim() ? `<div class="gp-submitted-quote">&ldquo;<span></span>&rdquo;</div>` : ""}
342
342
  </div>
343
343
  </div>`;
344
344
  }
345
345
  return `
346
346
  <div id="gp-finish-panel" class="gp-finish-panel">
347
- <div class="cdr-card">
348
- <div class="gp-finish-head">
349
- <h2 class="gp-finish-title">Finish your review</h2>
350
- </div>
351
- <div class="gp-finish-body">
352
- <div>
353
- <div class="gp-verdict-label">Your review</div>
354
- <div class="gp-verdict-options">
355
- <label class="cdr-choice">
356
- <input type="radio" name="verdict" data-action="set-verdict" value="approve" ${verdict === "approve" ? "checked" : ""} />
357
- <span class="cdr-box cdr-box--radio"></span>
358
- <span data-role="approve-label">${approveLabel}</span>
359
- </label>
360
- <label class="cdr-choice ${requestChangesDisabled ? "cdr-choice--disabled" : ""}" data-role="request-changes-choice">
361
- <input type="radio" name="verdict" data-action="set-verdict" value="request_changes" ${verdict === "request_changes" ? "checked" : ""} ${requestChangesDisabled ? "disabled" : ""} />
362
- <span class="cdr-box cdr-box--radio"></span>
363
- Request changes
364
- </label>
365
- </div>
366
- <div class="gp-verdict-hint" data-role="verdict-hint" ${requestChangesDisabled ? "" : 'style="display:none"'}>Leave at least one comment — on a section heading, an item, or below — to request changes.</div>
367
- </div>
368
- <textarea class="cdr-textarea" data-role="global-draft" placeholder="Overall feedback for the agent…" rows="4"></textarea>
369
- <div class="gp-finish-actions">
370
- <button type="button" class="cdr-btn cdr-btn--lg" data-action="submit-review" ${cannotSubmit ? "disabled" : ""}>Submit review</button>
347
+ <h2 class="gp-finish-title">Finish your review</h2>
348
+ <div class="gp-finish-body">
349
+ <div>
350
+ <div class="gp-verdict-label">Your review</div>
351
+ <div class="gp-verdict-options">
352
+ <label class="cdr-choice">
353
+ <input type="radio" name="verdict" data-action="set-verdict" value="approve" ${verdict === "approve" ? "checked" : ""} />
354
+ <span class="cdr-box cdr-box--radio"></span>
355
+ <span data-role="approve-label">${approveLabel}</span>
356
+ </label>
357
+ <label class="cdr-choice ${requestChangesDisabled ? "cdr-choice--disabled" : ""}" data-role="request-changes-choice">
358
+ <input type="radio" name="verdict" data-action="set-verdict" value="request_changes" ${verdict === "request_changes" ? "checked" : ""} ${requestChangesDisabled ? "disabled" : ""} />
359
+ <span class="cdr-box cdr-box--radio"></span>
360
+ Request changes
361
+ </label>
371
362
  </div>
363
+ <div class="gp-verdict-hint" data-role="verdict-hint" ${requestChangesDisabled ? "" : 'style="display:none"'}>Leave at least one comment — on a section heading, an item, or below — to request changes.</div>
364
+ </div>
365
+ <textarea class="cdr-textarea" data-role="global-draft" placeholder="Overall feedback for the agent…" rows="4"></textarea>
366
+ <div class="gp-finish-actions">
367
+ <button type="button" class="cdr-btn cdr-btn--lg" data-action="submit-review" ${cannotSubmit ? "disabled" : ""}>Submit review</button>
372
368
  </div>
373
369
  </div>
374
370
  </div>`;
@@ -457,18 +453,6 @@ function toggleCollapse(sectionId) {
457
453
  body.style.display = nowCollapsed ? "none" : "";
458
454
  chevronBtn.innerHTML = iconSvg(nowCollapsed ? "chevron-right" : "chevron-down");
459
455
  }
460
- function toggleReviewed(sectionId) {
461
- const nowReviewed = !state.reviewed.has(sectionId);
462
- if (nowReviewed) {
463
- state.reviewed.add(sectionId);
464
- state.collapsed.add(sectionId);
465
- }
466
- else {
467
- state.reviewed.delete(sectionId);
468
- state.collapsed.delete(sectionId);
469
- }
470
- renderApp();
471
- }
472
456
  function jumpToComment(blockId) {
473
457
  const section = currentSections.find((s) => s.headingBlockId === blockId || s.items.some((it) => it.id === blockId));
474
458
  if (section && state.collapsed.has(section.id)) {
@@ -512,6 +496,17 @@ function updateVerdictAvailability() {
512
496
  if (submitBtn)
513
497
  submitBtn.disabled = !effectiveVerdict() || state.submitted;
514
498
  }
499
+ function copyCode(button, blockId) {
500
+ const codeEl = appEl.querySelector(`[data-fence-for="${blockId}"] pre code`);
501
+ const text = codeEl?.textContent ?? "";
502
+ const original = button.textContent;
503
+ navigator.clipboard?.writeText(text).then(() => {
504
+ button.textContent = "Copied";
505
+ setTimeout(() => {
506
+ button.textContent = original ?? "Copy";
507
+ }, 1200);
508
+ }, () => { });
509
+ }
515
510
  function toggleTheme() {
516
511
  const root = document.documentElement;
517
512
  const next = root.getAttribute("data-theme") === "dark" ? "light" : "dark";
@@ -583,6 +578,10 @@ appEl.addEventListener("click", (e) => {
583
578
  case "toggle-theme":
584
579
  toggleTheme();
585
580
  break;
581
+ case "copy-code":
582
+ if (blockId)
583
+ copyCode(target, blockId);
584
+ break;
586
585
  case "submit-review":
587
586
  submitReview().catch(() => {
588
587
  // Leaves the panel interactive so the reviewer can just try again.
@@ -593,10 +592,7 @@ appEl.addEventListener("click", (e) => {
593
592
  appEl.addEventListener("change", (e) => {
594
593
  const target = e.target;
595
594
  const action = target.dataset.action;
596
- if (action === "toggle-reviewed" && target.dataset.sectionId) {
597
- toggleReviewed(target.dataset.sectionId);
598
- }
599
- else if (action === "set-verdict" && target instanceof HTMLInputElement) {
595
+ if (action === "set-verdict" && target instanceof HTMLInputElement) {
600
596
  setVerdict(target.value);
601
597
  }
602
598
  });