@bakapiano/ccsm 0.22.3 → 0.22.5

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 (61) hide show
  1. package/CLAUDE.md +538 -538
  2. package/README.md +189 -189
  3. package/bin/ccsm.js +235 -235
  4. package/lib/cliActivity.js +139 -139
  5. package/lib/codexSeed.js +183 -183
  6. package/lib/config.js +274 -274
  7. package/lib/devices.js +229 -229
  8. package/lib/folders.js +124 -124
  9. package/lib/localCliSessions.js +519 -519
  10. package/lib/persistedSessions.js +129 -129
  11. package/lib/tunnel.js +621 -621
  12. package/lib/webTerminal.js +225 -225
  13. package/lib/workspace.js +233 -233
  14. package/package.json +57 -57
  15. package/public/css/base.css +99 -99
  16. package/public/css/cards.css +183 -183
  17. package/public/css/feedback.css +504 -504
  18. package/public/css/forms.css +453 -453
  19. package/public/css/layout.css +176 -176
  20. package/public/css/modal.css +190 -190
  21. package/public/css/responsive.css +176 -176
  22. package/public/css/sidebar.css +707 -707
  23. package/public/css/terminals.css +645 -543
  24. package/public/css/tokens.css +81 -81
  25. package/public/css/wco.css +196 -196
  26. package/public/css/widgets.css +2725 -2725
  27. package/public/index.html +152 -152
  28. package/public/js/api.js +371 -371
  29. package/public/js/backend.js +149 -149
  30. package/public/js/components/App.js +73 -73
  31. package/public/js/components/DirectoryPicker.js +203 -203
  32. package/public/js/components/EntityFormModal.js +153 -153
  33. package/public/js/components/Modal.js +57 -57
  34. package/public/js/components/OfflineBanner.js +67 -67
  35. package/public/js/components/PageTitleBar.js +13 -13
  36. package/public/js/components/PendingApprovalOverlay.js +128 -128
  37. package/public/js/components/Picker.js +179 -179
  38. package/public/js/components/Popover.js +55 -55
  39. package/public/js/components/RestartOverlay.js +36 -36
  40. package/public/js/components/Sidebar.js +380 -380
  41. package/public/js/components/TerminalInstance.js +159 -22
  42. package/public/js/components/TerminalResizeDebouncer.js +126 -0
  43. package/public/js/components/TerminalView.js +15 -2
  44. package/public/js/components/XtermTerminal.js +74 -15
  45. package/public/js/components/useDragSort.js +67 -67
  46. package/public/js/dialog.js +67 -67
  47. package/public/js/icons.js +212 -212
  48. package/public/js/main.js +296 -296
  49. package/public/js/pages/AboutPage.js +90 -90
  50. package/public/js/pages/ConfigurePage.js +713 -713
  51. package/public/js/pages/LaunchPage.js +421 -421
  52. package/public/js/pages/RemotePage.js +743 -743
  53. package/public/js/pages/SessionsPage.js +199 -80
  54. package/public/js/state.js +335 -335
  55. package/public/manifest.webmanifest +25 -0
  56. package/public/setup/index.html +567 -0
  57. package/scripts/dev.js +149 -149
  58. package/scripts/install.js +153 -153
  59. package/scripts/restart-helper.js +96 -96
  60. package/scripts/upgrade-helper.js +687 -687
  61. package/server.js +1807 -1807
@@ -1,81 +1,81 @@
1
- /* ccsm · design tokens · v0.6 */
2
-
3
- :root {
4
- /* Surfaces — pale near-white tinted with the default Ocean accent.
5
- These match what state.js · applyAccentCssVars computes for the
6
- #2f6fa3 default, so the first paint (before the inline accent
7
- script in index.html runs) never flashes the old warm-cream bg. */
8
- --bg: #f6f8fa;
9
- --bg-elev: #ffffff;
10
- --sidebar-bg: #f6f8fa;
11
- --sidebar-hover: #e2ebf3;
12
- --sidebar-active: #d3e1ed;
13
-
14
- /* Neutral UI chrome — borders, footer strip, etc. These are NEVER
15
- re-derived from the accent so dividers stay calm regardless of
16
- theme choice. */
17
- --ui-border: #d8d4c6;
18
- --ui-border-soft: #e6e2d4;
19
- --ui-bg: #e2ebf3;
20
-
21
- /* Borders & rules — tinted with the default Ocean accent to match
22
- what state.js writes at boot. */
23
- --border: #d3e1ed;
24
- --border-soft: #d8e4ee;
25
- --border-strong: #c0d5e5;
26
-
27
- /* Ink */
28
- --ink: #1a1815;
29
- --ink-mid: #534e44;
30
- --ink-muted: #8a8475;
31
- --ink-faint: #b5af9d;
32
-
33
- /* Accent — Ocean blue. Calm and content-first. Users can override via
34
- the Theme accent picker in Settings; that flow rewrites these vars
35
- at runtime (state.js · applyAccentCssVars). */
36
- --accent: #2f6fa3;
37
- --accent-deep: #25577f;
38
- --accent-soft: rgba(47, 111, 163, 0.10);
39
- --accent-softer: rgba(47, 111, 163, 0.04);
40
-
41
- /* Status */
42
- --green: #4a8a4a;
43
- --yellow: #c4892b;
44
- --red: #b73f3f;
45
- --blue: #4a73a5;
46
-
47
- /* Type */
48
- --body: "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
49
- --display: "Geist", ui-sans-serif, system-ui, sans-serif;
50
- --mono: "JetBrains Mono", "Cascadia Mono", "Consolas", monospace;
51
-
52
- /* Scale */
53
- --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
54
- --s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
55
- --s-12: 48px; --s-16: 64px;
56
-
57
- /* Radius — tightened in v1.0 for a more codex-like crisp feel.
58
- 999px stays in code where pills are intentional (status dots etc). */
59
- --r-sm: 3px;
60
- --r: 4px;
61
- --r-md: 5px;
62
- --r-lg: 6px;
63
-
64
- /* Shadow — soft, like print on cream */
65
- --shadow-sm: 0 1px 0 rgba(26, 24, 21, 0.04);
66
- --shadow: 0 1px 2px rgba(26, 24, 21, 0.04),
67
- 0 1px 0 rgba(26, 24, 21, 0.03);
68
- --shadow-md: 0 4px 12px -2px rgba(26, 24, 21, 0.08),
69
- 0 1px 0 rgba(26, 24, 21, 0.04);
70
- --shadow-lg: 0 18px 40px -16px rgba(26, 24, 21, 0.18),
71
- 0 4px 12px -4px rgba(26, 24, 21, 0.10);
72
-
73
- /* Subtle paper grain — applied as a layered background on .app to give
74
- surfaces the texture of laid paper rather than flat color. SVG noise
75
- keeps it crisp at any zoom level. */
76
- --paper-noise: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.09 0 0 0 0 0.07 0 0 0 0.035 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
77
-
78
- /* Sidebar geometry */
79
- --sidebar-w: 232px;
80
- --sidebar-w-collapsed: 44px;
81
- }
1
+ /* ccsm · design tokens · v0.6 */
2
+
3
+ :root {
4
+ /* Surfaces — pale near-white tinted with the default Ocean accent.
5
+ These match what state.js · applyAccentCssVars computes for the
6
+ #2f6fa3 default, so the first paint (before the inline accent
7
+ script in index.html runs) never flashes the old warm-cream bg. */
8
+ --bg: #f6f8fa;
9
+ --bg-elev: #ffffff;
10
+ --sidebar-bg: #f6f8fa;
11
+ --sidebar-hover: #e2ebf3;
12
+ --sidebar-active: #d3e1ed;
13
+
14
+ /* Neutral UI chrome — borders, footer strip, etc. These are NEVER
15
+ re-derived from the accent so dividers stay calm regardless of
16
+ theme choice. */
17
+ --ui-border: #d8d4c6;
18
+ --ui-border-soft: #e6e2d4;
19
+ --ui-bg: #e2ebf3;
20
+
21
+ /* Borders & rules — tinted with the default Ocean accent to match
22
+ what state.js writes at boot. */
23
+ --border: #d3e1ed;
24
+ --border-soft: #d8e4ee;
25
+ --border-strong: #c0d5e5;
26
+
27
+ /* Ink */
28
+ --ink: #1a1815;
29
+ --ink-mid: #534e44;
30
+ --ink-muted: #8a8475;
31
+ --ink-faint: #b5af9d;
32
+
33
+ /* Accent — Ocean blue. Calm and content-first. Users can override via
34
+ the Theme accent picker in Settings; that flow rewrites these vars
35
+ at runtime (state.js · applyAccentCssVars). */
36
+ --accent: #2f6fa3;
37
+ --accent-deep: #25577f;
38
+ --accent-soft: rgba(47, 111, 163, 0.10);
39
+ --accent-softer: rgba(47, 111, 163, 0.04);
40
+
41
+ /* Status */
42
+ --green: #4a8a4a;
43
+ --yellow: #c4892b;
44
+ --red: #b73f3f;
45
+ --blue: #4a73a5;
46
+
47
+ /* Type */
48
+ --body: "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
49
+ --display: "Geist", ui-sans-serif, system-ui, sans-serif;
50
+ --mono: "JetBrains Mono", "Cascadia Mono", "Consolas", monospace;
51
+
52
+ /* Scale */
53
+ --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
54
+ --s-5: 20px; --s-6: 24px; --s-8: 32px; --s-10: 40px;
55
+ --s-12: 48px; --s-16: 64px;
56
+
57
+ /* Radius — tightened in v1.0 for a more codex-like crisp feel.
58
+ 999px stays in code where pills are intentional (status dots etc). */
59
+ --r-sm: 3px;
60
+ --r: 4px;
61
+ --r-md: 5px;
62
+ --r-lg: 6px;
63
+
64
+ /* Shadow — soft, like print on cream */
65
+ --shadow-sm: 0 1px 0 rgba(26, 24, 21, 0.04);
66
+ --shadow: 0 1px 2px rgba(26, 24, 21, 0.04),
67
+ 0 1px 0 rgba(26, 24, 21, 0.03);
68
+ --shadow-md: 0 4px 12px -2px rgba(26, 24, 21, 0.08),
69
+ 0 1px 0 rgba(26, 24, 21, 0.04);
70
+ --shadow-lg: 0 18px 40px -16px rgba(26, 24, 21, 0.18),
71
+ 0 4px 12px -4px rgba(26, 24, 21, 0.10);
72
+
73
+ /* Subtle paper grain — applied as a layered background on .app to give
74
+ surfaces the texture of laid paper rather than flat color. SVG noise
75
+ keeps it crisp at any zoom level. */
76
+ --paper-noise: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.10 0 0 0 0 0.09 0 0 0 0 0.07 0 0 0 0.035 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
77
+
78
+ /* Sidebar geometry */
79
+ --sidebar-w: 232px;
80
+ --sidebar-w-collapsed: 44px;
81
+ }
@@ -1,196 +1,196 @@
1
- /* PWA / --app= window drag regions.
2
- *
3
- * `-webkit-app-region: drag` is a Chromium-only property; it has effect in
4
- * Electron, installed PWA windows, AND in `--app=` chromeless mode where
5
- * the manifest's display_override unlocks WCO-ish behaviour. In a plain
6
- * browser tab it's silently ignored. We therefore apply it *unconditionally*
7
- * (no `@media (display-mode: …)` gate) so any surface that does honour it
8
- * gets the drag handles — particularly the case where Edge `--app=` reads
9
- * our manifest, hides its own OS title bar, but still reports
10
- * display-mode: browser. Without unconditional rules, that window has
11
- * neither a native title bar to grab NOR app-region drag → undraggable.
12
- */
13
-
14
- .sidebar-brand,
15
- .sidebar-top,
16
- .page-title-bar,
17
- .page-head,
18
- .page-head-inner,
19
- .page-title,
20
- .page-subtitle {
21
- -webkit-app-region: drag;
22
- app-region: drag;
23
- }
24
-
25
- /* Any interactive element opts out of drag so click / focus / scroll
26
- still reach the page. */
27
- button, a, input, select, textarea, label,
28
- .nav-item, .util-item, .collapse-toggle,
29
- .action, .server-status,
30
- .star-btn, .rename-btn, .card-fold, .modal-close,
31
- .chip, .radio, .fab,
32
- .ph-stat, .ph-divider,
33
- .page-title-bar-actions,
34
- .session-title-action {
35
- -webkit-app-region: no-drag;
36
- app-region: no-drag;
37
- }
38
-
39
- body.is-app .sidebar-brand,
40
- body.is-app .sidebar-top,
41
- body.is-app .page-title-bar,
42
- body.is-app .page-head,
43
- body.is-app .page-head-inner,
44
- body.is-app .page-title,
45
- body.is-app .page-subtitle {
46
- user-select: none;
47
- -webkit-user-select: none;
48
- }
49
-
50
- /* In an app/PWA window, the OS title bar is hidden and only floating
51
- close/max/min controls remain in the top-right. The 40px top band of
52
- ccsm (sidebar-top + page-title-bar) IS that title bar — it's already
53
- tall enough, sits at y=0 with no extra padding, and gets a drag region
54
- so the user can grab anywhere along it to move the window. */
55
- body.is-app .sidebar { padding-top: 0; }
56
- body.is-app .main { padding-top: 0; }
57
-
58
- body.is-app .page-title,
59
- body.is-app .page-subtitle {
60
- display: none;
61
- }
62
- body.is-app .page-head {
63
- padding-bottom: 0;
64
- border-bottom: 0;
65
- align-items: center;
66
- min-height: 28px;
67
- }
68
-
69
- /* Standalone PWA (browser still paints its own title bar above our
70
- document): suppress our duplicate chrome — the brand mark + collapse
71
- row at top-left of the sidebar AND the page-title-bar header — since
72
- the OS title bar already shows the app name and serves as the visual
73
- header. With sidebar-top hidden, the nav tabs (Sessions/Launch/…)
74
- float up to start at the very top of the sidebar column. A 1px
75
- border-top across the whole app grid gives the OS title bar something
76
- to rest against instead of bleeding straight into our content. */
77
- body.is-app:not(.is-wco) .sidebar-top {
78
- display: none;
79
- }
80
- body.is-app:not(.is-wco) .page-title-bar {
81
- display: none;
82
- }
83
- /* terminals.css uses `.page-title-bar + .session-tabs { margin-top: -s-4 }`
84
- to flush the tab strip against the in-page title-bar. Adjacent-sibling
85
- selectors match by DOM order regardless of display, so the rule still
86
- pulls session-tabs up by 16px even when the title-bar is hidden — the
87
- top half of the tabs ends up clipped above the viewport / under the OS
88
- title bar. Reset to 0 in standalone PWA. */
89
- body.is-app:not(.is-wco) .page-title-bar + .session-tabs {
90
- margin-top: 0;
91
- }
92
- body.is-app:not(.is-wco) .app {
93
- /* Hairline separator under the OS title bar. Has to sit ABOVE the
94
- sidebar's own background — an inset box-shadow on .app gets covered
95
- by .sidebar (which paints var(--ui-bg) across its full area inside
96
- .app), so the line was invisible. An absolutely-positioned ::before
97
- paints over both columns; it's outside the grid track so it doesn't
98
- reintroduce the 1px overflow that a real `border-top: 1px` did. */
99
- position: relative;
100
- }
101
- body.is-app:not(.is-wco) .app::before {
102
- content: "";
103
- position: absolute;
104
- top: 0;
105
- left: 0;
106
- right: 0;
107
- height: 1px;
108
- background: var(--border);
109
- z-index: 10;
110
- pointer-events: none;
111
- }
112
- /* With page-title-bar hidden, session-pane's `margin-top: calc(-1 *
113
- var(--s-4))` — designed to flush it against the (now invisible)
114
- title-bar — would otherwise yank the terminal up into the OS title-bar
115
- border. Zero the margin AND drop session-pane's `height: 100%` so flex
116
- does all the height math; otherwise the height: 100% double-counts and
117
- creates dead space at the bottom. */
118
- body.is-app:not(.is-wco) .session-pane {
119
- margin-top: 0;
120
- height: auto;
121
- }
122
- /* Settings + Launch · with the in-page title-bar hidden in standalone
123
- mode, their content would otherwise butt straight against the OS
124
- title-bar border. Give a little breathing room. Sessions has its
125
- own full-bleed terminal pane; About has its own hero header. */
126
- body.is-app:not(.is-wco) [data-panel="configure"],
127
- body.is-app:not(.is-wco) [data-panel="launch"],
128
- body.is-app:not(.is-wco) [data-panel="remote"] {
129
- padding-top: var(--s-4);
130
- }
131
- /* Sidebar nav rows (New Session / Settings) also need a top gap in
132
- standalone — body.is-app zeros .sidebar's padding-top so the in-page
133
- title-bar can sit flush, but with that title-bar gone in standalone
134
- mode the nav buttons end up jammed against the OS title-bar border.
135
- Restore a small inset so they read as a real nav, not as overflow. */
136
- body.is-app:not(.is-wco) .sidebar { padding-top: var(--s-3); }
137
-
138
- /* WCO mode only: the browser has hidden its own title bar and floats OS
139
- controls (min/max/close) over our content top-right. Our 34px top band
140
- IS the title bar in that case — it gets shrunk to match the OS control
141
- strip height, and reserves a right-side padding so action buttons don't
142
- slide under those floating controls. 150px covers Windows controls
143
- (~46px × 3) with breathing room; the env() override below uses the
144
- precise titlebar-area-width when the browser exposes it.
145
- In plain standalone PWA (browser still paints its own title bar above
146
- our document), none of this applies — our page-title-bar behaves like
147
- a normal 40px page header from layout.css. */
148
- body.is-wco .page-title-bar {
149
- padding-right: calc(var(--s-4) + 150px);
150
- }
151
- body.is-wco .page-title-bar,
152
- body.is-wco .sidebar-top {
153
- /* --titlebar-h is set from JS reading
154
- navigator.windowControlsOverlay.getTitlebarAreaRect().height — the
155
- OS-reported strip the overlay reserves for us. CSS env() and a 32px
156
- baseline are layered fallbacks when the JS API is unavailable. No
157
- min-floor on the JS path: the OS knows its own caption height best,
158
- and over-padding (the previous max(40px, …)) made the chrome look
159
- visibly chunkier than the rest of the window. */
160
- /* `max(36px, …)` floors the anti-zoom shrink — at heavy page zoom the
161
- counter-scale would otherwise collapse the WCO strip past the OS
162
- control row height, jamming the brand mark / collapse toggle into
163
- the title text. 36px matches the standalone page-title-bar floor
164
- in layout.css so both modes degrade to the same minimum. */
165
- height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
166
- min-height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
167
- max-height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
168
- }
169
- body.is-wco .sidebar-brand,
170
- body.is-wco .sidebar-brand-button,
171
- body.is-wco .collapse-toggle {
172
- height: var(--titlebar-h, env(titlebar-area-height, 32px));
173
- min-height: var(--titlebar-h, env(titlebar-area-height, 32px));
174
- }
175
- /* terminals.css uses the .tab-panel's gap (s-4) plus a -s-4 margin-top on
176
- .session-tabs to close that gap, so the tab strip visually flushes
177
- against the page-title-bar above. In WCO the negative margin pulls the
178
- tabs row UP by 16px — i.e. its top edge lands ABOVE the OS overlay's
179
- bottom edge, and the kebab inside gets clipped by the floating window
180
- controls. Cancel both the gap and the negative margin here so the tabs
181
- row sits at exactly y=titlebar-area-height (flush below the overlay)
182
- without losing flushness against the title-bar. */
183
- body.is-wco .tab-panel { gap: 0; }
184
- body.is-wco .page-title-bar + .session-tabs { margin-top: 0; }
185
- /* terminals.css also gives .session-tabs `margin-bottom: -s-4` to close
186
- the gap to session-pane below — but with tab-panel gap now 0 in WCO
187
- that negative margin pulls session-pane UP 16px instead, overlapping
188
- the bottom of the tab labels (kebab + tab text get clipped from
189
- below). Zero it out too. */
190
- body.is-wco .session-tabs { margin-bottom: 0; }
191
-
192
- @media (display-mode: window-controls-overlay) {
193
- body.is-wco .page-title-bar {
194
- padding-right: calc(var(--s-4) + 100vw - env(titlebar-area-width, 100vw));
195
- }
196
- }
1
+ /* PWA / --app= window drag regions.
2
+ *
3
+ * `-webkit-app-region: drag` is a Chromium-only property; it has effect in
4
+ * Electron, installed PWA windows, AND in `--app=` chromeless mode where
5
+ * the manifest's display_override unlocks WCO-ish behaviour. In a plain
6
+ * browser tab it's silently ignored. We therefore apply it *unconditionally*
7
+ * (no `@media (display-mode: …)` gate) so any surface that does honour it
8
+ * gets the drag handles — particularly the case where Edge `--app=` reads
9
+ * our manifest, hides its own OS title bar, but still reports
10
+ * display-mode: browser. Without unconditional rules, that window has
11
+ * neither a native title bar to grab NOR app-region drag → undraggable.
12
+ */
13
+
14
+ .sidebar-brand,
15
+ .sidebar-top,
16
+ .page-title-bar,
17
+ .page-head,
18
+ .page-head-inner,
19
+ .page-title,
20
+ .page-subtitle {
21
+ -webkit-app-region: drag;
22
+ app-region: drag;
23
+ }
24
+
25
+ /* Any interactive element opts out of drag so click / focus / scroll
26
+ still reach the page. */
27
+ button, a, input, select, textarea, label,
28
+ .nav-item, .util-item, .collapse-toggle,
29
+ .action, .server-status,
30
+ .star-btn, .rename-btn, .card-fold, .modal-close,
31
+ .chip, .radio, .fab,
32
+ .ph-stat, .ph-divider,
33
+ .page-title-bar-actions,
34
+ .session-title-action {
35
+ -webkit-app-region: no-drag;
36
+ app-region: no-drag;
37
+ }
38
+
39
+ body.is-app .sidebar-brand,
40
+ body.is-app .sidebar-top,
41
+ body.is-app .page-title-bar,
42
+ body.is-app .page-head,
43
+ body.is-app .page-head-inner,
44
+ body.is-app .page-title,
45
+ body.is-app .page-subtitle {
46
+ user-select: none;
47
+ -webkit-user-select: none;
48
+ }
49
+
50
+ /* In an app/PWA window, the OS title bar is hidden and only floating
51
+ close/max/min controls remain in the top-right. The 40px top band of
52
+ ccsm (sidebar-top + page-title-bar) IS that title bar — it's already
53
+ tall enough, sits at y=0 with no extra padding, and gets a drag region
54
+ so the user can grab anywhere along it to move the window. */
55
+ body.is-app .sidebar { padding-top: 0; }
56
+ body.is-app .main { padding-top: 0; }
57
+
58
+ body.is-app .page-title,
59
+ body.is-app .page-subtitle {
60
+ display: none;
61
+ }
62
+ body.is-app .page-head {
63
+ padding-bottom: 0;
64
+ border-bottom: 0;
65
+ align-items: center;
66
+ min-height: 28px;
67
+ }
68
+
69
+ /* Standalone PWA (browser still paints its own title bar above our
70
+ document): suppress our duplicate chrome — the brand mark + collapse
71
+ row at top-left of the sidebar AND the page-title-bar header — since
72
+ the OS title bar already shows the app name and serves as the visual
73
+ header. With sidebar-top hidden, the nav tabs (Sessions/Launch/…)
74
+ float up to start at the very top of the sidebar column. A 1px
75
+ border-top across the whole app grid gives the OS title bar something
76
+ to rest against instead of bleeding straight into our content. */
77
+ body.is-app:not(.is-wco) .sidebar-top {
78
+ display: none;
79
+ }
80
+ body.is-app:not(.is-wco) .page-title-bar {
81
+ display: none;
82
+ }
83
+ /* terminals.css uses `.page-title-bar + .session-tabs { margin-top: -s-4 }`
84
+ to flush the tab strip against the in-page title-bar. Adjacent-sibling
85
+ selectors match by DOM order regardless of display, so the rule still
86
+ pulls session-tabs up by 16px even when the title-bar is hidden — the
87
+ top half of the tabs ends up clipped above the viewport / under the OS
88
+ title bar. Reset to 0 in standalone PWA. */
89
+ body.is-app:not(.is-wco) .page-title-bar + .session-tabs {
90
+ margin-top: 0;
91
+ }
92
+ body.is-app:not(.is-wco) .app {
93
+ /* Hairline separator under the OS title bar. Has to sit ABOVE the
94
+ sidebar's own background — an inset box-shadow on .app gets covered
95
+ by .sidebar (which paints var(--ui-bg) across its full area inside
96
+ .app), so the line was invisible. An absolutely-positioned ::before
97
+ paints over both columns; it's outside the grid track so it doesn't
98
+ reintroduce the 1px overflow that a real `border-top: 1px` did. */
99
+ position: relative;
100
+ }
101
+ body.is-app:not(.is-wco) .app::before {
102
+ content: "";
103
+ position: absolute;
104
+ top: 0;
105
+ left: 0;
106
+ right: 0;
107
+ height: 1px;
108
+ background: var(--border);
109
+ z-index: 10;
110
+ pointer-events: none;
111
+ }
112
+ /* With page-title-bar hidden, session-pane's `margin-top: calc(-1 *
113
+ var(--s-4))` — designed to flush it against the (now invisible)
114
+ title-bar — would otherwise yank the terminal up into the OS title-bar
115
+ border. Zero the margin AND drop session-pane's `height: 100%` so flex
116
+ does all the height math; otherwise the height: 100% double-counts and
117
+ creates dead space at the bottom. */
118
+ body.is-app:not(.is-wco) .session-pane {
119
+ margin-top: 0;
120
+ height: auto;
121
+ }
122
+ /* Settings + Launch · with the in-page title-bar hidden in standalone
123
+ mode, their content would otherwise butt straight against the OS
124
+ title-bar border. Give a little breathing room. Sessions has its
125
+ own full-bleed terminal pane; About has its own hero header. */
126
+ body.is-app:not(.is-wco) [data-panel="configure"],
127
+ body.is-app:not(.is-wco) [data-panel="launch"],
128
+ body.is-app:not(.is-wco) [data-panel="remote"] {
129
+ padding-top: var(--s-4);
130
+ }
131
+ /* Sidebar nav rows (New Session / Settings) also need a top gap in
132
+ standalone — body.is-app zeros .sidebar's padding-top so the in-page
133
+ title-bar can sit flush, but with that title-bar gone in standalone
134
+ mode the nav buttons end up jammed against the OS title-bar border.
135
+ Restore a small inset so they read as a real nav, not as overflow. */
136
+ body.is-app:not(.is-wco) .sidebar { padding-top: var(--s-3); }
137
+
138
+ /* WCO mode only: the browser has hidden its own title bar and floats OS
139
+ controls (min/max/close) over our content top-right. Our 34px top band
140
+ IS the title bar in that case — it gets shrunk to match the OS control
141
+ strip height, and reserves a right-side padding so action buttons don't
142
+ slide under those floating controls. 150px covers Windows controls
143
+ (~46px × 3) with breathing room; the env() override below uses the
144
+ precise titlebar-area-width when the browser exposes it.
145
+ In plain standalone PWA (browser still paints its own title bar above
146
+ our document), none of this applies — our page-title-bar behaves like
147
+ a normal 40px page header from layout.css. */
148
+ body.is-wco .page-title-bar {
149
+ padding-right: calc(var(--s-4) + 150px);
150
+ }
151
+ body.is-wco .page-title-bar,
152
+ body.is-wco .sidebar-top {
153
+ /* --titlebar-h is set from JS reading
154
+ navigator.windowControlsOverlay.getTitlebarAreaRect().height — the
155
+ OS-reported strip the overlay reserves for us. CSS env() and a 32px
156
+ baseline are layered fallbacks when the JS API is unavailable. No
157
+ min-floor on the JS path: the OS knows its own caption height best,
158
+ and over-padding (the previous max(40px, …)) made the chrome look
159
+ visibly chunkier than the rest of the window. */
160
+ /* `max(36px, …)` floors the anti-zoom shrink — at heavy page zoom the
161
+ counter-scale would otherwise collapse the WCO strip past the OS
162
+ control row height, jamming the brand mark / collapse toggle into
163
+ the title text. 36px matches the standalone page-title-bar floor
164
+ in layout.css so both modes degrade to the same minimum. */
165
+ height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
166
+ min-height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
167
+ max-height: max(36px, calc(var(--titlebar-h, env(titlebar-area-height, 32px)) * var(--anti-zoom, 1)));
168
+ }
169
+ body.is-wco .sidebar-brand,
170
+ body.is-wco .sidebar-brand-button,
171
+ body.is-wco .collapse-toggle {
172
+ height: var(--titlebar-h, env(titlebar-area-height, 32px));
173
+ min-height: var(--titlebar-h, env(titlebar-area-height, 32px));
174
+ }
175
+ /* terminals.css uses the .tab-panel's gap (s-4) plus a -s-4 margin-top on
176
+ .session-tabs to close that gap, so the tab strip visually flushes
177
+ against the page-title-bar above. In WCO the negative margin pulls the
178
+ tabs row UP by 16px — i.e. its top edge lands ABOVE the OS overlay's
179
+ bottom edge, and the kebab inside gets clipped by the floating window
180
+ controls. Cancel both the gap and the negative margin here so the tabs
181
+ row sits at exactly y=titlebar-area-height (flush below the overlay)
182
+ without losing flushness against the title-bar. */
183
+ body.is-wco .tab-panel { gap: 0; }
184
+ body.is-wco .page-title-bar + .session-tabs { margin-top: 0; }
185
+ /* terminals.css also gives .session-tabs `margin-bottom: -s-4` to close
186
+ the gap to session-pane below — but with tab-panel gap now 0 in WCO
187
+ that negative margin pulls session-pane UP 16px instead, overlapping
188
+ the bottom of the tab labels (kebab + tab text get clipped from
189
+ below). Zero it out too. */
190
+ body.is-wco .session-tabs { margin-bottom: 0; }
191
+
192
+ @media (display-mode: window-controls-overlay) {
193
+ body.is-wco .page-title-bar {
194
+ padding-right: calc(var(--s-4) + 100vw - env(titlebar-area-width, 100vw));
195
+ }
196
+ }