@shardflux/desktop-linux-arm64 0.0.0-stage → 0.1.0-next.4

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.
@@ -0,0 +1,212 @@
1
+ @font-face{font-family:Funnel Sans;src:url(assets/FunnelSans.ttf)}@font-face{font-family:Funnel Display;src:url(assets/FunnelDisplay.woff2);font-weight:300 800}@font-face{font-family:Fragment Mono;src:url(assets/FragmentMono.ttf)}
2
+ :root{color-scheme:dark;--ob-bg:#000;--ob-tile:#0a0a0b;--ob-ink:#f5f3ef;--ob-muted:#8f8f95;--ob-dim:#5b5b61;--ob-line:rgba(255,255,255,.08);--ob-edge:rgba(255,255,255,.14)}
3
+ *{box-sizing:border-box}body{margin:0;background:var(--ob-bg);color:var(--ob-ink);font:14px/1.5 'Funnel Sans',sans-serif}button,input,textarea,select{font:inherit}button{cursor:pointer;color:inherit;border:1px solid var(--ob-edge);background:transparent;border-radius:999px;padding:9px 18px;white-space:nowrap}button:hover{background:rgba(255,255,255,.07)}button:disabled{opacity:.4;cursor:default}button.primary{background:var(--ob-ink);border-color:var(--ob-ink);color:#000;font-weight:500}button.primary:hover{background:#fff}button.link{border:0;color:var(--ob-muted);padding:4px 8px}button.small{font-size:12.5px;padding:6px 16px}button.icon{padding:5px 10px}a{color:inherit}input,textarea{color:inherit;background:rgba(255,255,255,.03);border:1px solid var(--ob-edge);border-radius:12px;padding:10px 16px;outline:none}input:focus,textarea:focus,button:focus-visible{outline:2px solid var(--sf-success);outline-offset:3px}h1,h2,h3,p{margin:0}h1,h2{font-family:'Funnel Display';font-weight:400;line-height:1.1;letter-spacing:-.045em}h1{font-size:34px}h2{font-size:26px}p{color:var(--ob-muted)}.row{display:flex;align-items:center;gap:12px}.spread{justify-content:space-between}.column{display:flex;flex-direction:column;gap:16px}.muted{color:var(--ob-muted)}.mono,kbd{font-family:'Fragment Mono';font-variant-numeric:tabular-nums}.error{color:var(--sf-warning);font-size:13px;padding:8px 0}.onboarding{height:100vh;min-height:520px;display:flex;flex-direction:column;gap:12px;padding:12px 20px 20px}.top{height:36px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.brand{display:flex;gap:10px;align-items:center;font:600 17px 'Funnel Display';letter-spacing:-.03em}.brand img{height:19px;width:19px}.rail{display:flex;align-items:center;border:1px solid var(--ob-edge);border-radius:999px;padding:3px;font-size:12.5px;gap:2px;color:var(--ob-dim)}.rail span{padding:4px 10px;border-radius:999px;white-space:nowrap}.rail span.active{background:rgba(255,255,255,.08);color:var(--ob-ink)}.rail span.done{color:var(--ob-muted)}.tile{flex:1;min-height:0;position:relative;overflow:hidden;background:var(--ob-tile);border:1px solid var(--ob-line);border-radius:24px;padding:28px;display:flex;flex-direction:column}.tile:before{content:'';position:absolute;bottom:-75px;right:-90px;width:190px;height:190px;background:url(assets/onboarding-wash-warm.webp) center/cover;border-radius:50%;pointer-events:none}.tile>*{position:relative}.subtitle{font-size:15.5px;line-height:1.6;max-width:520px;margin-top:12px}.stage{flex:1;display:flex;justify-content:center;align-items:center;flex-direction:column;gap:16px;min-height:0}.foot{display:flex;align-items:center;justify-content:space-between;min-height:46px;gap:12px}.foot .hint{font-size:12.5px;color:var(--ob-dim)}.signin{flex:1;background:url(assets/onboarding-silk.jpg) center/cover;display:flex;align-items:center;padding:40px 20px}.signin>div{max-width:430px}.signin h1{font-size:44px}.signin p{margin-top:16px;font-size:15.5px;max-width:420px}.signin .actions{margin-top:28px}.signin .terms{font-size:12.5px;line-height:1.4}.connectors{display:flex;flex-direction:column;gap:12px;margin-top:28px}.connector{display:flex;align-items:center;gap:16px;min-height:70px;padding:14px 20px;border:1px solid var(--ob-edge);border-radius:12px;background:rgba(255,255,255,.025)}.connector img{width:28px;height:28px;border-radius:6px}.connector .name{font-weight:500;font-size:15px}.connector .fact{font-size:12.5px;color:var(--ob-muted)}.connector .status{margin-left:auto;font-size:12.5px;white-space:nowrap}.connector.connected .status{color:var(--sf-success);font-weight:500}.onboarding .connector.connected{border-color:var(--sf-success);box-shadow:0 0 0 3px rgba(139,202,161,.55),0 0 30px rgba(139,202,161,.25)}.copy-task{display:flex;gap:18px;align-items:center;border:1px solid var(--ob-edge);border-radius:12px;background:rgba(255,255,255,.03);padding:14px;max-width:560px}.copy-task button{flex-shrink:0}.keycaps{display:flex;gap:12px}.keycaps kbd{border:1px solid var(--ob-edge);border-bottom-width:4px;border-radius:10px;padding:12px 18px;font-size:26px;background:rgba(255,255,255,.03)}.keycaps kbd.held{transform:translateY(3px);border-bottom-width:1px;background:rgba(255,255,255,.12)}.screen{overflow:hidden;border:1px solid var(--ob-edge);border-radius:12px;background:#000;position:relative;min-height:200px}.screen iframe,.screen img{width:100%;height:100%;border:0;object-fit:cover;pointer-events:none}.live-layout{display:grid;grid-template-columns:1fr 240px;gap:24px;flex:1;min-height:0;margin-top:20px}.feed{font-size:13px;display:flex;flex-direction:column;gap:12px;overflow:auto}.phone{padding:16px;border:1px solid var(--ob-edge);border-radius:12px;margin-top:14px}.phone .mono{font-size:20px}.empty{padding:28px;text-align:center}.enter{animation:enter .24s ease-out}@keyframes enter{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important}}@media(max-width:640px){.onboarding{padding:12px}.top .rail span{padding:4px 6px;font-size:11px}.tile{padding:24px 18px}.title-row{flex-wrap:wrap}.live-layout{grid-template-columns:1fr}.signin{padding:28px 16px}.signin h1{font-size:34px}.foot .hint{display:none}}
4
+ /* Match the Mac title rail and the small ribbon crop without drawing platform chrome. */
5
+ .top{position:relative}.top>.rail{position:absolute;left:50%;transform:translateX(-50%)}.top>.link{margin-left:auto}.brand img{filter:invert(1)}.signin>div{transform:translateY(12px)}.signin .terms{margin-top:0}.tile:before{width:340px;height:240px;bottom:-80px;right:-60px;background-position:bottom right;border-radius:0}
6
+ .agent-icon{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:6px;flex-shrink:0}.agent-icon.claude{background:var(--sf-molten)}.agent-icon.claude img{filter:brightness(0) invert(1);width:23px;height:23px}.connector .agent-icon img{border-radius:4px}.tile:before{mix-blend-mode:screen}.own-shortcut{border:1px solid var(--ob-edge);border-radius:14px;padding:14px 18px;margin-top:28px;max-width:600px}.own-shortcut .keycaps{gap:6px}.own-shortcut kbd{font-size:16px;padding:6px 12px;border-bottom-width:2px}.desktop-result .top{display:none}.desktop-result .tile{border:0;background:transparent;padding:28px 10px}.desktop-result .tile:before{display:none}.desktop-result h1{font-size:30px}.desktop-result .subtitle{max-width:none}.desktop-result .live-layout{grid-template-columns:260px 1fr;align-items:center;gap:28px}.desktop-result .screen{grid-column:2;grid-row:1;width:100%;height:320px}.desktop-result .feed{grid-column:1;grid-row:1;align-self:start;padding-top:20px}.screen svg{width:100%;height:100%;display:block}
7
+ .settings{height:100vh;display:grid;grid-template-columns:190px 1fr;background:var(--sf-bg)}.settings aside{background:var(--sf-raise);border-right:1px solid var(--sf-line);padding:28px 12px}.sidebar-brand{display:flex;gap:10px;align-items:center;font:600 20px 'Funnel Display';margin:0 10px 24px}.sidebar-brand img{width:24px;height:32px}.settings nav{display:flex;flex-direction:column;gap:4px}.settings nav button{text-align:left;border:0;border-radius:6px;padding:10px 14px;color:var(--sf-muted)}.settings nav button.selected{background:var(--sf-overlay);color:var(--sf-ink)}.settings-content{overflow:auto;padding:28px 24px;display:flex;flex-direction:column;gap:16px}.pane-description{margin-top:-8px;font-size:13px}.setting-group{border:1px solid var(--sf-line);border-radius:8px;background:rgba(255,255,255,.035);overflow:hidden}.setting-row{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:12px;font-size:13px}.setting-row+.setting-row{border-top:1px solid var(--sf-line)}.setting-row b{font-weight:500}.setting-row p{font-size:12.5px;margin-top:4px}.setting-row>div:last-child{flex-shrink:0}.settings select{background:var(--sf-overlay);color:var(--sf-ink);border:1px solid var(--sf-line-2);border-radius:6px;padding:6px 10px;font:13px 'Funnel Sans';max-width:180px}.setting-row input[role=switch]{appearance:none;width:32px;height:19px;border-radius:20px;padding:0;background:var(--sf-dim);position:relative;border:0;cursor:pointer}.setting-row input[role=switch]:after{content:'';position:absolute;left:2px;top:2px;width:15px;height:15px;border-radius:50%;background:var(--sf-ink);transition:left .12s}.setting-row input[role=switch]:checked{background:var(--sf-success)}.setting-row input[role=switch]:checked:after{left:15px}.account-heading{display:flex;align-items:center;gap:18px;padding:16px}.account-heading img{width:44px;height:52px}.account-heading h2{font:500 20px 'Funnel Sans';letter-spacing:0}.account-plan{padding:0 16px 16px}.settings-cards{display:flex;flex-direction:column;gap:14px}.settings .connector{border:0;background:transparent;padding:16px 12px}.settings .setting-note{font-size:12.5px}.settings a{font-size:12.5px;text-decoration:none}
8
+ .permission,.files-window{padding:30px;display:flex;flex-direction:column;gap:18px;min-height:100vh;background:var(--sf-bg)}.permission{max-width:700px;margin:auto}.permission h2{font-size:26px}.permission-details{border:1px solid var(--sf-line);border-radius:10px;padding:18px;display:flex;flex-direction:column;gap:16px;background:var(--sf-raise)}.permission pre{white-space:pre-wrap;word-break:break-word;font:13px/1.6 'Fragment Mono';margin:0}.permission-path{display:flex;flex-direction:column;gap:6px}.permission-path small{color:var(--sf-muted)}.permission-path code{font:13px 'Fragment Mono';overflow-wrap:anywhere}.permission-always{display:flex;align-items:center;gap:10px;font-size:13px}.permission footer{margin-top:auto}.permission label{display:flex;flex-direction:column;gap:8px}.permission .permission-always{flex-direction:row}.permission select{padding:10px;color:var(--sf-ink);background:var(--sf-overlay);border:1px solid var(--sf-line-2);border-radius:6px}.files-window code{color:var(--sf-muted);font:12.5px 'Fragment Mono';overflow-wrap:anywhere}.files-window h3{font-size:14px;font-weight:500;margin-top:16px}
9
+ /* Shared geometry follows AgentAppIcon, AgentConnectorCard and LauncherResultRow. */
10
+ :root{--system-accent:#0a84ff}.ui-icon{display:inline-block;vertical-align:-.16em;flex-shrink:0}.app-mark{display:inline-block;object-fit:contain;border-radius:22%;vertical-align:middle;flex-shrink:0}.missing-mark{width:36px;height:36px;background:#ffffff14}.connector .app-mark{width:36px;height:36px}.connector{gap:12px;padding:16px;background:#101011;position:relative;isolation:isolate}.connector .status{display:flex;align-items:center;gap:5px;font-size:13px}.onboarding .connector.connected{border-color:rgba(139,202,161,.7);box-shadow:0 0 26px rgba(139,202,161,.35)}.onboarding .connector.connected:before,.onboarding .connector.connected:after{content:'';position:absolute;inset:-4px;border-radius:10px;z-index:-1;pointer-events:none}.onboarding .connector.connected:before{background:rgba(139,202,161,.42);filter:blur(22px);z-index:-2}.onboarding .connector.connected:after{border:3px solid rgba(139,202,161,.9);filter:blur(5px)}.onboarding .connector.connected{background:#111614}.settings .connector{box-shadow:none}.settings .connector:before,.settings .connector:after{display:none}.rail span{display:flex;align-items:center;gap:5px}.foot>div:empty{flex:1}.foot button{display:inline-flex;align-items:center;gap:8px}.title-row{justify-content:flex-start;gap:18px}.title-row .rail{flex-shrink:0}button .ui-icon{pointer-events:none}
11
+ .permission{padding:24px;gap:20px;min-height:100vh;background:#1d1d20}.permission-heading{display:flex;gap:12px;align-items:flex-start}.permission-heading h2{font:500 20px/1.2 'Funnel Sans';letter-spacing:0}.permission-heading p{font-size:12.5px;margin-top:6px}.permission-icon{width:40px;height:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--sf-warning);background:#e8bd6b14;border-radius:8px}.permission-details{padding:12px;gap:12px;background:#ffffff09}.permission pre{padding:12px;background:var(--sf-bg);border-radius:8px;font-size:13px}.permission p{font-size:12.5px}.permission small{display:block;font-size:12.5px;color:var(--sf-muted)}.permission footer{border-top:1px solid var(--sf-line);padding-top:20px}.permission button{border-radius:6px;font-size:13px}
12
+
13
+ /* Keep the bloom outside the opaque tile, as the Mac card's background layers do. */
14
+ .onboarding .connector.connected{background:#111213;box-shadow:0 0 6px 2px rgba(139,202,161,.55),0 0 26px 6px rgba(139,202,161,.30)}.onboarding .connector.connected:before,.onboarding .connector.connected:after{display:none}
15
+
16
+ .permission{min-height:0}.permission footer{margin-top:0}.update-notice{display:flex;align-items:center;gap:10px;padding:12px}
17
+ /* AgentAppIcon: a tile on 82% of the frame (the macOS icon grid), 22.5% corner radius. */
18
+ .app-mark{width:var(--mark,16px);height:var(--mark,16px);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;vertical-align:middle;border-radius:0;object-fit:initial}
19
+ .app-mark>i,.app-mark>img{width:82%;height:82%;border-radius:22.5%;display:block}
20
+ .app-mark.claude>i{background:#d97757;position:relative}
21
+ .app-mark.claude>i:after{content:'';position:absolute;inset:19%;background:#fff;-webkit-mask:url(assets/claude-mark.png) center/contain no-repeat;mask:url(assets/claude-mark.png) center/contain no-repeat}
22
+ .app-mark.codex>img{box-shadow:inset 0 0 0 .5px #0000001f}
23
+ .connector .app-mark{width:36px;height:36px}
24
+ .ui-spinner{animation:spin .9s steps(8) infinite;flex-shrink:0;vertical-align:middle}
25
+ @keyframes spin{to{transform:rotate(360deg)}}
26
+ /* LiveCard: the window becomes the agent's computer, with its own navigation (no rail, no Back). */
27
+ .live-layout-window{padding:46px 24px 20px;gap:0}
28
+ .live-card{flex:1;display:flex;flex-direction:column;min-height:0}
29
+ .live-card h1{font-size:28px;letter-spacing:-.04em;white-space:nowrap}
30
+ .live-card .subtitle{font-size:14px;margin-top:8px;max-width:none}
31
+ .live-screen{flex:1;margin-top:16px;border-radius:14px;min-height:0}
32
+ .live-screen .empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:var(--ob-muted);font-size:13px}
33
+ .activity-line{margin-top:12px;font-size:13px;color:var(--ob-muted);min-height:20px}
34
+ .recap{display:flex;gap:18px;margin-top:16px;align-items:flex-start;flex:1;min-height:0}
35
+ .recap-lines{width:250px;flex-shrink:0;display:flex;flex-direction:column;gap:12px;padding-top:4px}
36
+ .recap-line{display:flex;align-items:baseline;gap:10px;font-size:14.5px;color:var(--ob-ink);animation:enter .32s ease-out both;animation-delay:calc(var(--i)*70ms)}
37
+ .recap-line .ui-icon{stroke-width:2.2;flex-shrink:0;transform:translateY(1px)}
38
+ .recap-lines p{font-size:13px;margin-top:6px}
39
+ .recap-shot{flex:1;aspect-ratio:1.3;max-height:100%;border-radius:14px;border:1px solid var(--ob-edge);animation:enter .32s ease-out .12s both}
40
+ .live-foot{display:flex;align-items:center;gap:14px;margin-top:8px;font-size:13px;min-height:40px}
41
+ .live-foot .grow{flex:1}
42
+ .live-foot .report{margin-left:-8px;display:inline-flex;align-items:center;gap:6px;color:var(--ob-muted);font-size:13px}
43
+ .live-foot .primary{display:inline-flex;align-items:center;gap:7px;font-size:14px;padding:8px 16px}
44
+ /* DoneStep and sign-in: homepage moments on the silk. */
45
+ .onboarding.hero{background:url(assets/onboarding-silk.jpg) center/cover}
46
+ .onboarding.hero .signin{background:none}
47
+ .finale{flex:1;display:flex;flex-direction:column;justify-content:flex-end;padding:0 30px 96px 24px}
48
+ .finale h1{font-size:50px;max-width:380px}
49
+ .finale .subtitle{max-width:380px;margin-top:16px}
50
+ .finale .primary{align-self:flex-start;margin-top:30px;display:inline-flex;align-items:center;gap:9px}
51
+ /* Shared by the bar and the top-bar panel: the Mac's solid surface (ShardfluxGlass .solid: sf-overlay, a 16% ink
52
+ edge), its secondary ink, and native selection in the system accent. */
53
+ :root{--glass-secondary:rgba(255,255,255,.78);--surface:#1b1c1f;--accent-text:#fff}
54
+ body:has(.launcher),body:has(.menu-panel){background:transparent;overflow:hidden}
55
+ .state-dot{width:6px;height:6px;border-radius:50%;display:inline-block;flex-shrink:0;background:var(--sf-dim)}
56
+ .state-dot.working{background:var(--sf-molten)}
57
+ .state-dot.needs-you{background:var(--sf-ink)}
58
+ .state-dot.sign-in{background:var(--sf-warning)}
59
+ .state-dot.parked{background:var(--sf-muted)}
60
+ .state-dot.quiet,.state-dot.done{background:var(--sf-dim)}
61
+ .menu-rule{height:1px;background:var(--sf-line);margin:3px 12px}
62
+ /* LauncherView: 620 wide, 12 px corners, rows of 34 px. */
63
+ .launcher{width:620px;margin:15px;background:var(--surface);border:1px solid var(--sf-line-2);border-radius:12px;box-shadow:0 18px 40px -18px #000c,0 2px 10px #0006;color:var(--sf-ink);overflow:hidden;cursor:default;user-select:none}
64
+ .launcher button{border:0;border-radius:6px;padding:0;background:transparent;color:inherit;font:inherit;display:inline-flex;align-items:center;justify-content:center;white-space:nowrap}
65
+ .launcher button:disabled{opacity:.45;cursor:default}
66
+ .launcher button:focus-visible,.launcher input:focus,.launcher textarea:focus{outline:none}
67
+ .prompt-row{display:flex;align-items:flex-start;gap:12px;padding:20px 20px 16px}
68
+ .prompt-row .lead{height:24px;display:flex;align-items:center;color:var(--glass-secondary)}
69
+ .prompt-row textarea{flex:1;min-height:24px;height:24px;max-height:96px;padding:0;margin:0;border:0;border-radius:0;background:transparent;resize:none;font:17px/24px 'Funnel Sans';color:var(--sf-ink);overflow:auto;user-select:text}
70
+ .prompt-row textarea::placeholder{color:var(--sf-muted)}
71
+ .prompt-row .progress{height:24px;display:flex;align-items:center;color:var(--glass-secondary)}
72
+ .prompt-row .add{width:28px;height:26px;margin:-1px 0 0}
73
+ .launcher .control{height:26px;padding:0 8px;gap:6px;font-size:13px;color:var(--sf-ink)}
74
+ .launcher .control:hover:not(:disabled),.launcher .control.active{background:rgba(237,235,231,.10)}
75
+ .launcher .control:active:not(:disabled){background:rgba(237,235,231,.18)}
76
+ .launcher .control.icon-only{width:28px;padding:0}
77
+ .launcher .control.chosen{background:var(--system-accent);color:var(--accent-text)}
78
+ .launcher .control.chosen:hover{background:var(--system-accent)}
79
+ .launcher .control .ui-icon[width="9"]{margin-top:1px}
80
+ .launcher-controls{display:flex;align-items:center;padding:0 20px 16px}
81
+ .launcher-controls .grow{flex:1;min-width:12px}
82
+ .launcher .project{margin-left:-8px;color:var(--sf-muted);min-width:0;max-width:230px}
83
+ .launcher .project.empty{color:var(--sf-ink)}
84
+ .launcher .project-name{overflow:hidden;text-overflow:ellipsis;min-width:0}
85
+ .launcher .dropdowns{display:flex;gap:2px;margin-right:8px}
86
+ .launcher .pill.secondary{color:var(--glass-secondary)}
87
+ .launcher .pill.dimmed{opacity:.45}
88
+ .launcher .tools{display:flex;gap:4px;margin-right:8px}
89
+ .launcher .start{height:28px;padding:0 12px;gap:7px;border-radius:7px;background:var(--sf-ink);color:#15161a;font-size:12.5px;font-weight:500;box-shadow:inset 0 1px 0 #fff8,0 1px 2px #0005}
90
+ .launcher .start:hover:not(:disabled){background:#fff}
91
+ .launcher .start.off,.launcher .start:disabled{opacity:1;background:rgba(237,235,231,.03);color:rgba(237,235,231,.45);box-shadow:inset 0 0 0 1px var(--sf-line-2)}
92
+ .launcher .key-hint{display:inline-flex;align-items:center;gap:1px;opacity:.65;font-size:12px}
93
+ .launcher .start .app-mark{margin-right:-1px}
94
+ .launcher .branch{margin:0 20px 12px;width:calc(100% - 40px);height:28px;padding:0;border:0;background:transparent;font-size:13px;color:var(--sf-ink)}
95
+ .launcher .attachments{display:flex;gap:8px;padding:0 20px 12px;overflow-x:auto}
96
+ .launcher .attachments.with-images{padding-top:6px}
97
+ .context-chip{gap:5px;height:22px;padding:0 8px!important;border-radius:4px!important;background:rgba(237,235,231,.06)!important;color:var(--glass-secondary)!important;font-size:11px!important}
98
+ .context-chip:hover{background:rgba(237,235,231,.10)!important}
99
+ .image-chip{position:relative;flex-shrink:0;width:56px;height:40px}
100
+ .image-chip img{width:56px;height:40px;object-fit:cover;border-radius:6px;box-shadow:inset 0 0 0 1px var(--sf-line-2);outline:1px solid var(--sf-line-2);outline-offset:-1px;cursor:pointer;display:block}
101
+ .image-chip.selected img{outline:1.5px solid rgba(237,235,231,.85)}
102
+ .image-chip .image-remove{position:absolute;top:-6px;right:-6px;width:16px;height:16px;border-radius:50%;background:var(--sf-overlay);box-shadow:inset 0 0 0 1px var(--sf-line-2);color:var(--sf-ink);display:none}
103
+ .image-chip:hover .image-remove,.image-chip.selected .image-remove{display:inline-flex}
104
+ .image-preview{padding:0 20px 14px;display:flex;justify-content:center;cursor:pointer}
105
+ .image-preview img{max-width:580px;max-height:240px;border-radius:6px;outline:1px solid var(--sf-line-2)}
106
+ .launcher .separator{height:1px;background:var(--sf-line)}
107
+ .menu-list,.project-section{padding:8px;display:flex;flex-direction:column;gap:2px}
108
+ .project-section .project-field{height:35px;padding:0 8px;margin:0;border:0;border-radius:0;background:transparent;font-size:14px;color:var(--sf-ink);user-select:text}
109
+ .project-section .project-field::placeholder{color:var(--sf-muted)}
110
+ .project-rows{overflow-y:auto;display:flex;flex-direction:column;gap:2px;position:relative}
111
+ .project-rows .no-match{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--glass-secondary)}
112
+ .result-row{width:100%;min-height:34px;flex-shrink:0;justify-content:flex-start!important;gap:10px;padding:0 12px!important;text-align:left;color:var(--sf-ink)}
113
+ .result-row .row-icon{width:16px;display:inline-flex;justify-content:center;color:var(--glass-secondary)}
114
+ .result-row .result-title{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis}
115
+ .result-row .result-detail{margin-left:auto;font-size:12.5px;color:var(--glass-secondary);overflow:hidden;text-overflow:ellipsis;max-width:62%;padding-left:12px}
116
+ .result-row .row-check{width:14px;display:inline-flex;justify-content:center;margin-left:0}
117
+ .result-row .result-detail:empty+.row-check{margin-left:auto}
118
+ .result-row .row-check.hidden{visibility:hidden}
119
+ .result-row.selected{background:var(--system-accent);color:var(--accent-text)}
120
+ .result-row.selected .row-icon{color:rgba(255,255,255,.9)}
121
+ .result-row.selected .result-detail,.result-row.selected .thread-state{color:rgba(255,255,255,.82)}
122
+ .result-row.selected .state-dot{background:#fff}
123
+ .result-row:active{box-shadow:inset 0 0 0 100px rgba(0,0,0,.18)}
124
+ .computer-row{align-items:flex-start!important;padding:7px 12px!important}
125
+ .computer-row .row-icon{height:20px;align-items:center}
126
+ .computer-row .threads{flex:1;display:flex;flex-direction:column;gap:4px;min-width:0}
127
+ .thread-line{display:flex;align-items:center;gap:8px;height:20px}
128
+ .thread-title{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;flex:1}
129
+ .thread-state{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--glass-secondary);margin-left:8px}
130
+ .thread-more{font-size:12.5px;color:var(--glass-secondary);height:18px}
131
+ .computer-row .row-check{height:20px;align-items:center}
132
+ .menu-note{display:flex;align-items:center;gap:8px;min-height:30px;padding:0 12px;font-size:12.5px;color:var(--glass-secondary)}
133
+ .launcher-note{display:flex;align-items:center;gap:8px;padding:16px;font-size:13px;color:var(--glass-secondary)}
134
+ .launcher-error{padding:16px;display:flex;flex-direction:column;align-items:flex-start;gap:8px}
135
+ .launcher-error p{display:flex;align-items:flex-start;gap:8px;font-size:13px;line-height:18px;color:var(--sf-danger)}
136
+ .launcher-error p .ui-icon{margin-top:2px}
137
+ .launcher-error .error-actions{display:flex;gap:8px}
138
+ .launcher button.bordered,.menu-panel button.bordered{height:24px;padding:0 10px;border-radius:5px;background:rgba(237,235,231,.16);box-shadow:inset 0 0.5px 0 rgba(255,255,255,.18);font-size:13px}
139
+ .launcher button.bordered:hover{background:rgba(237,235,231,.22)}
140
+ /* AgentMenu: the top-bar panel, 420 wide with 36 px rows; menus open over it at its bottom right. */
141
+ .menu-panel{position:relative;width:420px;margin:12px;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--sf-line-2);border-radius:12px;box-shadow:0 18px 40px -18px #000c,0 2px 10px #0006;color:var(--sf-ink);font-size:13px;overflow:hidden;user-select:none;cursor:default}
142
+ .menu-panel.with-overflow{min-height:calc(215px + var(--placeholders) * 30px)}
143
+ .menu-panel.with-actions{min-height:232px}
144
+ .menu-panel button{border:0;background:transparent;color:inherit;font:inherit;padding:0;border-radius:4px;display:flex;align-items:center;white-space:nowrap}
145
+ .menu-panel button:focus-visible{outline:2px solid var(--system-accent);outline-offset:-2px}
146
+ .menu-panel .new-conversation{height:36px;margin:4px;padding:0 16px;gap:8px;color:var(--glass-secondary);font-size:14px}
147
+ .menu-panel .new-conversation .ui-icon{width:16px}
148
+ .menu-panel .new-conversation:hover,.menu-panel .menu-item:hover,.menu-panel .menu-item:focus-visible{background:rgba(237,235,231,.07)}
149
+ .menu-panel .new-conversation .menu-title{flex:1;text-align:left}
150
+ .menu-panel .menu-shortcut{font-size:12.5px;color:var(--glass-secondary)}
151
+ .menu-panel>.menu-rule{margin:0 12px}
152
+ .session-list{padding:4px 0;overflow-y:auto}
153
+ .session-row{height:36px;margin:0 4px;padding:0 4px 0 12px;display:flex;align-items:center;gap:4px;border-radius:6px}
154
+ .session-row:hover,.session-row.actions-open{background:rgba(237,235,231,.06)}
155
+ .session-open{flex:1;min-width:0;height:100%;gap:8px}
156
+ .session-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;text-align:left}
157
+ .session-title.done{color:var(--sf-muted)}
158
+ .session-project{max-width:92px;overflow:hidden;text-overflow:ellipsis;font-size:12.5px;color:var(--glass-secondary)}
159
+ .session-age{width:34px;text-align:right;font:11px 'Fragment Mono';font-variant-numeric:tabular-nums;color:var(--glass-secondary)}
160
+ .row-tool{width:24px;height:28px;justify-content:center;color:var(--glass-secondary);opacity:0;pointer-events:none}
161
+ .session-row:hover .row-tool,.row-tool.pressed{opacity:1;pointer-events:auto}
162
+ .row-tool.pressed{background:rgba(237,235,231,.10)!important}
163
+ .done-toggle{width:100%;height:36px;padding:0 16px!important;gap:8px;color:var(--glass-secondary);font-size:12.5px}
164
+ .done-toggle .ui-icon{width:10px;stroke-width:2.6}
165
+ .menu-empty{padding:24px 16px;font-size:13px;color:var(--glass-secondary)}
166
+ .menu-error{display:flex;align-items:flex-start;gap:8px;padding:12px;color:var(--glass-secondary);font-size:12.5px}
167
+ .menu-error>.ui-icon{color:var(--sf-danger);margin-top:2px}
168
+ .menu-error p{color:inherit;font-size:12.5px}
169
+ .menu-error .plain{margin-top:4px;color:var(--sf-ink)}
170
+ .menu-panel .grow{flex:1}
171
+ .menu-foot{display:flex;justify-content:flex-end;padding:0 8px 4px}
172
+ .overflow-toggle{position:relative;width:32px;height:28px;justify-content:center;color:var(--glass-secondary)}
173
+ .overflow-toggle .ui-icon{stroke-width:2.4}
174
+ .update-dot{width:4px;height:4px;border-radius:50%;background:var(--sf-ink);display:inline-block}
175
+ .overflow-toggle .update-dot{position:absolute;top:4px;right:4px}
176
+ .menu-dismiss{position:absolute;inset:0}
177
+ .menu-popover{position:absolute;right:8px;bottom:36px;width:236px;padding:6px;background:var(--surface);border:1px solid var(--sf-line-2);border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,.24);display:flex;flex-direction:column}
178
+ .menu-popover.row-actions{width:252px}
179
+ .menu-popover .menu-heading{font-size:11.5px;color:var(--glass-secondary);padding:2px 6px 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
180
+ .menu-popover .menu-rule{margin:4px 6px}
181
+ .menu-item{height:30px;width:100%;padding:0 6px!important;gap:8px;font-size:13px}
182
+ .menu-item>.ui-icon{width:16px;color:var(--glass-secondary)}
183
+ .menu-item .menu-title{flex:1;text-align:left;overflow:hidden;text-overflow:ellipsis}
184
+ .menu-item .menu-shortcut{font-size:11px}
185
+ .menu-item .update-dot{margin-right:8px}
186
+ .approval-card{margin:8px;padding:12px;display:flex;gap:12px;align-items:flex-start;border:1px solid var(--sf-line);border-radius:8px;background:rgba(237,235,231,.04)}
187
+ .approval-icon{width:24px;height:28px;display:flex;align-items:center;justify-content:center;color:var(--glass-secondary)}
188
+ .approval-body{flex:1;min-width:0}
189
+ .approval-body b{font-weight:500;font-size:13px}
190
+ .approval-body p{margin:4px 0 8px;font-size:12.5px;color:var(--glass-secondary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
191
+ .approval-actions{display:flex;align-items:center;gap:12px;font-size:12.5px}
192
+ .approval-actions .grow{flex:1}
193
+ .approval-actions .glass-button{height:22px;padding:0 9px!important;border-radius:5px!important;background:rgba(237,235,231,.14)!important;box-shadow:inset 0 0.5px 0 rgba(255,255,255,.2);font-size:12px!important}
194
+ .approval-actions .plain{color:var(--glass-secondary)}
195
+ .approval-actions .more{width:20px;justify-content:center;color:var(--glass-secondary)}
196
+ /* ProgressRail: 28 px segments, 13 px type; RailSegment colours (current ink on a 7% pill, done muted, ahead dim). */
197
+ .rail{padding:3px;gap:2px}
198
+ .rail .rail-step{display:flex;align-items:center;gap:5px;height:28px;padding:0 10px;border:0;border-radius:999px;background:transparent;font-size:13px;color:var(--ob-dim);white-space:nowrap}
199
+ .rail .rail-step.active{background:rgba(255,255,255,.07);color:var(--ob-ink)}
200
+ .rail .rail-step.done,.rail .rail-step.reachable{color:var(--ob-muted)}
201
+ .rail button.rail-step:hover{color:var(--ob-ink);background:rgba(255,255,255,.04)}
202
+ .rail .rail-step .ui-icon{stroke-width:2}
203
+ /* AgentConnectorCard: 10 px corners, a near-black tile; connected cards in the first run are lit, a wide soft
204
+ bloom and a tighter bright edge of brand success outside the card (Settings keeps them plain). */
205
+ .connector{border-radius:10px;background:linear-gradient(rgba(237,235,231,.035),rgba(237,235,231,.035)),#0f0f11;border:1px solid var(--sf-line)}
206
+ .connector .name{font-size:15px;font-weight:500}
207
+ .connector .status{gap:6px;font-size:13px;font-weight:500}
208
+ .connector.connected .status{color:var(--sf-success)}
209
+ .onboarding .connector.connected{border-color:rgba(123,198,138,.7);background:linear-gradient(rgba(237,235,231,.035),rgba(237,235,231,.035)),#0f0f11;box-shadow:0 0 5px 1.5px rgba(123,198,138,.85),0 0 22px 5px rgba(123,198,138,.40),0 0 34px 6px rgba(123,198,138,.22)}
210
+ .settings .connector,.settings .connector.connected{box-shadow:none;border-color:transparent}
211
+ .menu-panel .session-title,.menu-panel .new-conversation,.menu-item{font-weight:450}
212
+ .menu-error .bordered{margin-top:6px;height:22px;font-size:12.5px}
@@ -0,0 +1,219 @@
1
+ // LauncherView.swift and LauncherController.swift: the floating bar. A prompt row (search glass, the task, + for
2
+ // context), then the project button, the app/model/effort dropdowns, the computer and capture buttons and Start. One
3
+ // menu opens under the bar at a time (the project list, a dropdown or the computer menu); the one highlight follows
4
+ // the pointer and the arrow keys, Return picks and the current choice has a checkmark.
5
+ import {icon,appMark,spinner} from './icons.mjs';
6
+ import {commands,recordStart,reference,composePrompt,modelEfforts,validChoice,matches,escape as e,t3Failure,failureReference,t3Remedy,t3RemedyTitle,t3EffortLine,effortNotApplied,effortKey,openTarget,barEffort,projectEntry} from './core.mjs';
7
+ const visibleRows=5,rowHeight=34,rowSpacing=2,fieldHeight=35;
8
+ export function installSurfaces({model,host,cli,save,savePrefs,render,button,registerActions,openConversation}) {
9
+ const L={prompt:'',path:model.prefs.detectProject===false?'':model.prefs.lastProject||model.p.sample_path||'',app:model.prefs.app||'claude',selection:0,picker:null,query:'',projects:[...(model.prefs.projects||[])],images:[],files:[],branch:'',showBranch:false,link:'',on:null,targets:null,computers:[],computersLoading:false,computersError:null,stage:null,note:null,remedy:null,created:null,error:'',selectedImage:null,preview:null};
10
+ model.launcherState=L;
11
+ const app=document.querySelector('#app');
12
+ const catalog=()=>L.targets?.models?.[L.app];
13
+ const choices=()=>validChoice((model.prefs.choices||{})[L.app]||{},catalog());
14
+ const target=()=>openTarget(L.targets,L.app);
15
+ // AppModel.startConversationTask: a new thread goes to T3 Code when that is where this app's threads open; the
16
+ // first run's sample starts as before, so its live view has the VM's key.
17
+ const handoff=()=>target()==='t3'&&L.path!==model.p.sample_path;
18
+ const effortNote=()=>target()==='t3'?(model.prefs.t3EffortReasons||{})[effortKey(L.app,choices().model)]:null;
19
+ const canStart=()=>!!L.prompt.trim()&&!!L.path&&!L.stage&&!L.created;
20
+ const clean=title=>(title||'New conversation').replace(/^(\[SF\]\s*|Shardflux · )/,'').split('\n')[0].trim()||'New conversation';
21
+ const efforts=()=>modelEfforts(catalog(),choices().model).filter(m=>m.id!=='default');
22
+ const conversations=()=>{const q=L.prompt.trim();return !q||L.picker?[]:(model.rows||[]).filter(r=>r.state!=='done'&&matches(`${r.title} ${r.project}`,q)).slice(0,5);};
23
+ const projectRows=()=>L.projects.filter(p=>matches(p,L.query)).map(projectEntry);
24
+ function items(){
25
+ const c=catalog(),chosen=choices();
26
+ if(L.picker==='project')return [...projectRows().map(p=>({label:p.name,detail:p.detail,value:p.path,icon:'folder'})),{label:'Choose folder…',value:'choose-folder',icon:'folder-plus'}];
27
+ if(L.picker==='app')return [{label:'Claude',value:'claude',app:'claude',checked:L.app==='claude'},{label:'Codex',value:'codex',app:'codex',checked:L.app==='codex'}];
28
+ if(L.picker==='model'){const d=c?.models?.find(m=>m.id===c.default?.model)?.label;return [{label:d?`Default (${d})`:'Default',value:null,checked:!chosen.model,rule:true},...(c?.models||[]).filter(m=>m.id!=='default').map(m=>({label:m.label||m.id,detail:m.description||'',value:m.id,checked:chosen.model===m.id}))];}
29
+ if(L.picker==='effort'){const d=c?.efforts?.find(m=>m.id===c.default?.effort)?.label;return [{label:d?`Default (${d})`:'Default',value:null,checked:!chosen.effort,rule:true},...efforts().map(m=>({label:m.label||m.id,value:m.id,checked:chosen.effort===m.id}))];}
30
+ if(L.picker==='computer')return [{label:'New computer',value:null,icon:'plus',checked:!L.on},...L.computers.map(c=>({computer:c,value:c.workspace_id,checked:L.on===c.workspace_id}))];
31
+ if(!L.picker)return conversations().map(r=>({label:r.title,detail:r.project,app:r.app,value:r.reference,conversation:true}));
32
+ return [];
33
+ }
34
+ const sel=i=>i===L.selection?'selected':'';
35
+ function resultRow(m,i){
36
+ const lead=m.app?appMark(m.app,16):m.icon?`<span class="row-icon">${icon(m.icon,13)}</span>`:'';
37
+ return `<button class="result-row ${sel(i)}" data-action="pick:${i}" data-index="${i}" role="option" aria-selected="${!!m.checked}">${lead}<span class="result-title">${e(m.label)}</span><span class="result-detail">${e(m.detail||'')}</span>${m.checked!==undefined&&m.checked?`<span class="row-check">${icon('check',13)}</span>`:''}</button>${m.rule?'<div class="menu-rule"></div>':''}`;
38
+ }
39
+ // LauncherComputerRow: a computer named by the threads already on it, each with its app, title and state.
40
+ function computerRow(m,i){
41
+ const threads=m.computer.conversations||[],label={working:'working',needs_you:'needs you',waiting:'needs you',unresolved:'unresolved'};
42
+ const lines=threads.slice(0,3).map(t=>`<span class="thread-line">${t.harness||t.app?appMark(t.harness||t.app,14):`<span class="row-icon small">${icon('message-circle',11)}</span>`}<span class="thread-title">${e(clean(t.title))}</span>${label[t.state]?`<span class="thread-state"><span class="state-dot ${t.state==='working'?'working':t.state==='unresolved'?'quiet':'needs-you'}"></span>${label[t.state]}</span>`:''}</span>`).join('')+(threads.length>3?`<span class="thread-more">${threads.length-3} more</span>`:'');
43
+ return `<button class="result-row computer-row ${sel(i)}" data-action="pick:${i}" data-index="${i}" title="${e(clean(m.computer.label||threads[0]?.title))}" aria-selected="${!!m.checked}"><span class="row-icon">${icon('desktop',13)}</span><span class="threads">${lines||`<span class="thread-line"><span class="thread-title">${e(clean(m.computer.label||m.computer.workspace_key))}</span></span>`}</span><span class="row-check ${m.checked?'':'hidden'}">${icon('check',13)}</span></button>`;
44
+ }
45
+ function menu(){
46
+ if(L.picker==='project'){
47
+ const rows=projectRows(),visible=Math.min(visibleRows,L.projects.length),list=items();
48
+ const height=visible===0?0:visible*rowHeight+(visible-1)*rowSpacing;
49
+ return `<div class="separator"></div><section class="project-section"><input class="project-field" name="project-search" placeholder="Find a project…" value="${e(L.query)}" autocomplete="off" spellcheck="false"><div class="project-rows" style="height:${height}px">${rows.length?list.slice(0,-1).map(resultRow).join(''):visible?'<p class="no-match">No matching projects</p>':''}</div>${resultRow(list.at(-1),list.length-1)}</section>`;
50
+ }
51
+ if(L.picker==='computer'){const list=items();return `<div class="separator"></div><section class="menu-list">${resultRow(list[0],0)}${list.slice(1).map((m,i)=>computerRow(m,i+1)).join('')}${list.length===1||L.computersError?`<p class="menu-note">${L.computersLoading?spinner(13):''}${e(L.computersLoading?'Finding this project’s computers…':L.computersError||'This project has no other computers yet.')}</p>`:''}</section>`;}
52
+ const list=items();if(!list.length)return '';
53
+ return `<div class="separator"></div><section class="menu-list ${L.picker?'':'results'}">${list.map(resultRow).join('')}</section>`;
54
+ }
55
+ // The pills: content then a small chevron; the one whose menu is open keeps the pressed fill.
56
+ const pill=(key,content,{cls='',title='',label=''}={})=>`<button class="control pill ${L.picker===key?'active':''} ${cls}" data-action="picker:${key}" title="${e(title)}" aria-label="${e(label)}" ${L.stage?'disabled':''}>${content}${icon('chevron-down',9)}</button>`;
57
+ function controls(){
58
+ const c=catalog(),chosen=choices(),t3=target()==='t3',note=effortNote();
59
+ const model_=c?.models?.find(m=>m.id===chosen.model),fallback=c?.models?.find(m=>m.id===c?.default?.model);
60
+ const effort_=c?.efforts?.find(m=>m.id===chosen.effort),effortDefault=c?.efforts?.find(m=>m.id===c?.default?.effort);
61
+ const name=L.path?L.path.replace(/\/+$/,'').split('/').at(-1):'Choose project';
62
+ const place={terminal:'your terminal',web:'your browser',t3:'T3 Code'}[target()]||'your terminal';
63
+ const t3Glyph=t3&&model.appIcons?.t3?appMark('t3',13,model.appIcons.t3):'';
64
+ const title=L.stage==='starting'?'Starting…':L.stage==='opening'?'Opening…':'Start';
65
+ return `<div class="launcher-controls">
66
+ <button class="control project ${L.picker==='project'?'active':''} ${L.path?'':'empty'}" data-action="picker:project" title="${e(L.path?L.path:'Choose a project (Tab)')}" ${L.stage?'disabled':''}>${icon('folder',13)}<span class="project-name">${e(name)}</span>${icon('chevron-down',9)}</button>
67
+ <span class="grow"></span>
68
+ <span class="dropdowns">${pill('app',appMark(L.app,14),{title:`${L.app==='codex'?'Codex':'Claude'} conversations ${t3?'start':'open'} in ${place}`,label:'Agent app'})}${c?.models?.length?pill('model',`<span>${e(model_?.short||fallback?.short||'Default')}</span>`,{cls:model_?'':'secondary',title:`${model_?'Model':'Default model'} (Ctrl+Shift+M)`,label:'Model'}):''}${c&&efforts().length?pill('effort',`<span>${e(effort_?barEffort(effort_):effortDefault?barEffort(effortDefault):'Default')}</span>`,{cls:`${effort_?'':'secondary'} ${note?'dimmed':''}`,title:note?t3EffortLine[note]:`${effort_?'Effort':'Default effort'} (Ctrl+Shift+E)`,label:'Effort'}):''}</span>
69
+ <span class="tools">${L.targets?.computers===false?'':`<button class="control icon-only computer ${L.picker==='computer'?'active':''} ${L.on?'chosen':''}" data-action="picker:computer" title="${e((L.on?clean(L.computers.find(c=>c.workspace_id===L.on)?.label):'New computer. Choose one of this project’s computers')+' (Ctrl+Shift+C)')}" aria-label="${L.on?'Chosen computer':'New computer'}" ${L.stage||!L.path?'disabled':''}>${icon('desktop',15)}</button>`}<button class="control icon-only" data-action="capture" title="${L.images.length>=5?'Up to five captures':'Capture part of the screen (Ctrl+Shift+S)'}" aria-label="Capture part of the screen" ${L.stage||L.images.length>=5?'disabled':''}>${icon('capture',15)}</button></span>
70
+ <button class="start ${canStart()?'':'off'}" data-action="start" title="${t3?'Start in T3 Code (Ctrl+Return)':'Start conversation (Ctrl+Return)'}" aria-label="${t3?'Start in T3 Code':'Start conversation'}" ${canStart()?'':'disabled'}>${t3Glyph}<span>${title}</span>${L.stage?'':`<span class="key-hint">Ctrl${icon('corner-down-left',11)}</span>`}</button>
71
+ </div>`;
72
+ }
73
+ function attachments(){
74
+ if(!L.images.length&&!L.files.length&&!L.link&&!L.branch.trim())return '';
75
+ const chip=(label,name,action)=>`<button class="context-chip" data-action="${action}" title="Remove ${e(label)}">${icon(name,11)}<span>${e(label)}</span></button>`;
76
+ return `<div class="attachments ${L.images.length?'with-images':''}">${L.images.map((im,i)=>`<div class="image-chip ${L.selectedImage===i?'selected':''}" title="Screenshot ${i+1}"><img data-action="preview:${i}" src="${im.data}" alt="Screenshot ${i+1}"><button class="image-remove" data-action="remove-image:${i}" aria-label="Remove screenshot ${i+1}">${icon('x',8)}</button></div>`).join('')}${L.files.map((f,i)=>chip(f.path.split('/').at(-1),'file',`remove-file:${i}`)).join('')}${L.link?chip((()=>{try{return new URL(L.link).host;}catch{return 'Link';}})(),'link','remove-link'):''}${L.branch.trim()?chip(L.branch.trim(),'git-branch','remove-branch'):''}</div>`;
77
+ }
78
+ function footerLines(){
79
+ let html='';
80
+ if(L.note)html+=`<div class="separator"></div><p class="launcher-note">${icon('info',14)}<span>${e(L.note)}</span></p>`;
81
+ const message=L.error;
82
+ if(message){
83
+ const mode=L.targets?.t3?.mode,title=L.remedy?t3RemedyTitle(L.remedy,mode):null;
84
+ html+=`<div class="separator"></div><div class="launcher-error"><p role="alert">${icon('circle-alert',14)}<span>${e(message)}</span></p>${title||(L.created&&!L.remedy?.threadID)?`<div class="error-actions">${title?`<button class="bordered" data-action="remedy" ${L.stage?'disabled':''}>${e(title)}</button>`:''}${L.created&&!L.remedy?.threadID?'<button class="bordered" data-action="open-created">Open created conversation</button>':''}</div>`:''}</div>`;
85
+ }
86
+ return html;
87
+ }
88
+ function renderLauncher(){
89
+ const area=document.activeElement?.name;
90
+ app.innerHTML=`<section class="launcher" aria-label="Launcher"><div class="prompt-row"><span class="lead">${icon('search',17)}</span><textarea name="prompt" aria-label="What should the agent do?" placeholder="What should the agent do?" rows="1" ${L.stage?'disabled':''}>${e(L.prompt)}</textarea>${L.stage?`<span class="progress">${spinner(14)}</span>`:''}<button class="control icon-only add" data-action="add" title="Add context" aria-label="Add context" ${L.stage?'disabled':''}>${icon('plus',14)}</button></div>${controls()}${L.showBranch?`<input class="branch" name="branch" aria-label="Branch context" placeholder="Branch context" value="${e(L.branch)}">`:''}${attachments()}${L.preview!==null&&L.images[L.preview]?`<div class="image-preview" data-action="close-preview" title="Click or press Esc to close the preview"><img src="${L.images[L.preview].data}" alt="Screenshot ${L.preview+1} preview"></div>`:''}<div class="results-slot">${menu()}</div>${footerLines()}</section>`;
91
+ fit();
92
+ if(area==='project-search'||L.picker==='project'){const input=app.querySelector('[name=project-search]');if(input&&document.activeElement!==input){input.focus();input.setSelectionRange(input.value.length,input.value.length);}}
93
+ else if(area==='prompt'||!area){const t=app.querySelector('[name=prompt]');if(t&&!L.stage){t.focus();t.setSelectionRange(t.value.length,t.value.length);}}
94
+ }
95
+ function fit(){requestAnimationFrame(()=>{const t=app.querySelector('textarea');if(t){t.style.height='auto';t.style.height=Math.min(96,t.scrollHeight)+'px';}const bar=app.querySelector('.launcher');if(bar)void host('resize',{height:Math.ceil(bar.getBoundingClientRect().height)+30});const s=app.querySelector('.result-row.selected');s?.scrollIntoView({block:'nearest'});});}
96
+ // A keystroke in the prompt updates only what it changes (Start, and the matching conversations), never the field.
97
+ function promptChanged(){
98
+ const start=app.querySelector('[data-action=start]');if(start){start.disabled=!canStart();start.classList.toggle('off',!canStart());}
99
+ const slot=app.querySelector('.results-slot');if(slot&&!L.picker){L.selection=0;slot.innerHTML=menu();}fit();
100
+ }
101
+ window.renderSurface=()=>{if(model.view==='launcher')renderLauncher();else app.innerHTML=`<div class="empty"><h2>${e(model.view)}</h2></div>`;};
102
+ async function refreshComputers(){
103
+ if(!L.path){L.computers=[];return;}L.computersLoading=true;L.computersError=null;
104
+ try{const r=await cli(['agents','computers','--path',L.path,'--json']);L.computers=(r.computers||[]).filter(c=>c.workspace_id);if(L.on&&!L.computers.some(c=>c.workspace_id===L.on))L.on=null;}
105
+ catch(err){L.computers=[];L.on=null;if(!/unknown (command|option)/i.test(err.message))L.computersError='Couldn’t list this project’s computers. Try again.';}
106
+ finally{L.computersLoading=false;}
107
+ }
108
+ async function load(){
109
+ try{L.targets=await cli(['agents','targets','--json']);}catch{L.targets={};}
110
+ for(const a of ['claude','codex'])(model.prefs.choices??={})[a]=validChoice((model.prefs.choices||{})[a]||{},L.targets.models?.[a]);
111
+ if(model.p.step===2&&model.p.sample_path)L.path=model.p.sample_path;
112
+ if(model.prefs.pendingComputer){const pending=model.prefs.pendingComputer;L.on=pending.id;const path=pending.path||L.projects.find(p=>p.split('/').at(-1)===pending.project);if(path)L.path=path;else {L.path='';L.error='Choose the local project folder for this computer.';}delete model.prefs.pendingComputer;await savePrefs();}
113
+ L.projects=[...new Set([...(model.p.sample_path?[model.p.sample_path]:[]),...L.projects])];
114
+ render();void refreshComputers();
115
+ try{const found=await host('projects');L.projects=[...new Set([...L.projects,...found])];model.prefs.projects=L.projects.slice(0,2000);await savePrefs();}catch{}
116
+ if(L.picker==='project')render();
117
+ }
118
+ // The project scan reports in batches as it walks the home folder, so the list grows while it is open.
119
+ window.addEventListener('sf-projects',ev=>{L.projects=[...new Set([...L.projects,...ev.detail])];if(model.view==='launcher'&&L.picker==='project'){const slot=app.querySelector('.results-slot');if(slot){slot.innerHTML=menu();fit();}}});
120
+ window.addEventListener('sf-view',ev=>{if(ev.detail==='launcher'){L.picker=null;void load();}});
121
+ window.addEventListener('sf-state',ev=>{if(ev.detail.name==='onboarding.json')model.p=ev.detail.value;if(ev.detail.name==='desktop-linux.json')model.prefs=ev.detail.value;if(model.view!=='launcher')render();});
122
+ window.addEventListener('sf-launcher-visible',ev=>{model.launcher=ev.detail;if(model.view==='onboarding')render();});
123
+ app.addEventListener('input',ev=>{if(model.view!=='launcher')return;
124
+ if(ev.target.name==='prompt'){L.prompt=ev.target.value;if(L.error){L.error='';L.remedy=null;L.created=null;render();return;}promptChanged();}
125
+ if(ev.target.name==='branch')L.branch=ev.target.value;
126
+ if(ev.target.name==='project-search'){L.query=ev.target.value;L.selection=0;const slot=app.querySelector('.results-slot');if(slot){slot.innerHTML=menu();const input=slot.querySelector('[name=project-search]');input.focus();input.setSelectionRange(L.query.length,L.query.length);fit();}}});
127
+ // Dragging from anywhere that is not a control moves the bar; a double-click there returns it to its default spot.
128
+ app.addEventListener('pointerdown',ev=>{if(model.view!=='launcher'||ev.button!==0||ev.detail>1)return;if(ev.target.closest('.launcher')&&!ev.target.closest('textarea,input,button,img,.result-row'))void host('drag',{x:Math.round(ev.screenX),y:Math.round(ev.screenY)});});
129
+ app.addEventListener('dblclick',ev=>{if(model.view==='launcher'&&!ev.target.closest('textarea,input,button,img'))void host('reset-position');});
130
+ function setPicker(key){if(L.stage)return;if(key==='model'&&!catalog()?.models?.length)return;if(key==='effort'&&!efforts().length)return;L.picker=L.picker===key?null:key;L.query='';const list=items();L.selection=Math.max(0,list.findIndex(m=>m.checked));if(key==='project')L.selection=Math.max(0,list.findIndex(m=>m.value===L.path));if(L.picker==='computer')void refreshComputers().then(()=>{if(L.picker==='computer')render();});render();}
131
+ document.addEventListener('keydown',ev=>{if(model.view!=='launcher')return;const list=items();
132
+ if(list.length&&['ArrowDown','ArrowUp'].includes(ev.key)&&!ev.shiftKey){ev.preventDefault();L.selection=(L.selection+(ev.key==='ArrowDown'?1:-1)+list.length)%list.length;app.querySelectorAll('[data-index]').forEach(r=>r.classList.toggle('selected',Number(r.dataset.index)===L.selection));app.querySelector('.result-row.selected')?.scrollIntoView({block:'nearest'});return;}
133
+ // As LauncherController: Ctrl+Return starts; Return accepts the highlighted row (Shift+Return is a new line).
134
+ if(ev.key==='Enter'&&(ev.ctrlKey||ev.metaKey)){ev.preventDefault();void perform('start');return;}
135
+ if(ev.key==='Enter'&&!ev.shiftKey){ev.preventDefault();if(list.length)void pick(L.selection);return;}
136
+ if(ev.key==='Escape'){ev.preventDefault();if(L.preview!==null)L.preview=null;else if(L.picker)L.picker=null;else if(L.selectedImage!==null)L.selectedImage=null;else{void host('hide');return;}render();return;}
137
+ if(ev.key==='Tab'&&!ev.shiftKey){ev.preventDefault();setPicker('project');return;}
138
+ if((ev.key==='Delete'||ev.key==='Backspace')&&L.selectedImage!==null&&document.activeElement?.name!=='prompt'){L.images.splice(L.selectedImage,1);L.selectedImage=null;L.preview=null;render();return;}
139
+ if(ev.ctrlKey&&ev.shiftKey){const k=ev.key.toLowerCase(),key={m:'model',e:'effort',c:'computer'}[k];if(key){ev.preventDefault();setPicker(key);}else if(k==='s'){ev.preventDefault();void perform('capture');}}});
140
+ app.addEventListener('pointermove',ev=>{if(model.view!=='launcher')return;const row=ev.target.closest('[data-index]');if(row&&Number(row.dataset.index)!==L.selection){L.selection=Number(row.dataset.index);app.querySelectorAll('[data-index]').forEach(r=>r.classList.toggle('selected',r===row));}});
141
+ async function capture(){
142
+ if(L.images.length>=5)throw new Error('Attach up to five captures.');
143
+ await host('hide');
144
+ try{const path=await host('capture');if(path){L.images.push({path,data:path.startsWith('data:')?path:await host('image',{path})});L.selectedImage=null;}}
145
+ finally{await host('view',{name:'launcher'});}
146
+ }
147
+ async function start(){
148
+ if(!canStart())return;
149
+ const handOff=handoff(),appName=L.app,chosen=choices(),images=L.images.slice();let promptFile;
150
+ L.error='';L.remedy=null;L.note=null;L.picker=null;L.stage='starting';model.error='';render();
151
+ try{
152
+ promptFile=await host('prompt',{text:composePrompt(L.prompt,L.branch.trim(),L.link,L.files)});
153
+ const doc=await cli(commands.create({app:appName,path:L.path,promptFile,computer:L.path===model.p.sample_path,on:L.on,...chosen,t3:handOff,images:images.length?['-']:[]}),{timeout:540,...(images.length?{images:images.map(i=>i.data.split(',')[1])}:{})});
154
+ L.images=L.images.filter(i=>!images.includes(i));L.preview=null;L.selectedImage=null;
155
+ let ref;
156
+ if(handOff){if(typeof doc.opened!=='boolean'&&doc.target!=='t3')throw new Error('shard returned no response.');ref=doc.workspace?.workspace_key||(/^conv_[a-z0-9]{26}$/.test(doc.conversation_id||doc.conversation?.id||'')?doc.conversation_id||doc.conversation.id:null);if(doc.t3_link)model.phone=doc.t3_link;}
157
+ else ref=reference(doc,!!L.on);
158
+ if(L.on)L.on=null;
159
+ if(ref){L.created=ref;const kind=recordStart(model.p,ref,L.path,appName);if(kind==='otherProject')L.error=`That one started in ${L.path.split('/').at(-1)}. For this demo, pick Daylight (sample).`;await save();}
160
+ model.prefs.lastProject=L.path;model.prefs.app=appName;await savePrefs();
161
+ if(handOff){
162
+ if(!doc.opened){if(!ref)throw new Error('shard returned no response.');L.stage='opening';render();await cli(['agents','open','t3',ref,'--json']);}
163
+ // A picked effort T3 Code did not take: said once, quietly, and that pill dims from then on.
164
+ const reason=effortNotApplied(doc,chosen.effort);
165
+ if(chosen.effort){const reasons=model.prefs.t3EffortReasons??={},key=effortKey(appName,chosen.model);if(reason)reasons[key]=reason;else delete reasons[key];await savePrefs();}
166
+ if(reason){L.stage='opening';L.note=t3EffortLine[reason]+'.';render();await new Promise(r=>setTimeout(r,2200));}
167
+ return finish();
168
+ }
169
+ L.stage='opening';render();
170
+ await openConversation(ref);
171
+ finish();
172
+ }catch(err){
173
+ const failure=t3Failure(err.detail);
174
+ if(failure){L.remedy=t3Remedy(failure,L.targets?.t3?.mode)?failure:null;if(failure.threadID)L.created=failureReference(failure)??`t3:${failure.threadID}`;else if(failure.conversation)L.created=failure.conversation;}
175
+ else if(err.detail?.details?.workspace_key)L.created=err.detail.details.workspace_key;
176
+ L.error=err.message;
177
+ }finally{L.stage=null;if(promptFile)await host('remove-prompt',{path:promptFile});render();}
178
+ }
179
+ async function finish(){L.created=null;L.note=null;L.prompt='';L.files=[];L.link='';L.branch='';L.showBranch=false;await host('hide');}
180
+ // The refused hand-off's one action: open the started thread (never a second start), T3 Code, or Settings.
181
+ async function remedy(){
182
+ const f=L.remedy,kind=t3Remedy(f,L.targets?.t3?.mode);if(!kind)return;
183
+ if(kind==='openThread'){const ref=failureReference(f);if(!ref){await host('open-app',{app:'t3'});L.remedy=null;await finish();return;}
184
+ L.error='';L.stage='opening';render();try{await cli(['agents','open','t3',ref,'--json']);L.remedy=null;await finish();}catch(err){const next=t3Failure(err.detail);if(next&&t3Remedy(next,L.targets?.t3?.mode))L.remedy=next;L.error=err.message;}finally{L.stage=null;render();}return;}
185
+ if(kind==='openT3'){await host('open-app',{app:'t3'});return;}
186
+ model.prefs.settingsPane=kind==='settings'?'Agents':'Updates';await savePrefs();await host('view',{name:'settings'});
187
+ }
188
+ async function pick(index){const m=items()[index];if(!m)return;
189
+ if(m.conversation){await openConversation(m.value);await host('hide');return;}
190
+ if(L.picker==='project'){if(m.value==='choose-folder')return perform('choose-project');L.path=m.value;L.picker=null;render();await refreshComputers();}
191
+ else if(L.picker==='app'){L.app=m.value;model.prefs.app=m.value;await savePrefs();}
192
+ else if(L.picker==='computer')L.on=m.value;
193
+ else if(L.picker){(model.prefs.choices??={})[L.app]=validChoice({...choices(),[L.picker]:m.value},catalog());await savePrefs();}
194
+ L.picker=null;L.selection=0;render();}
195
+ const actions={start,capture,remedy,
196
+ add:async(b)=>{const r=b.getBoundingClientRect();await host('popup',{x:Math.round(r.right),y:Math.round(r.bottom+4),width:1,height:1,align:'right',items:[{label:'Add files…',action:'add-files'},{label:'Add clipboard link',action:'add-link'},{label:'Add branch…',action:'add-branch'}]});},
197
+ 'open-created':()=>openConversation(L.created),
198
+ 'choose-project':async()=>{L.picker=null;const path=await host('choose',{kind:'folder',path:L.path});if(path){L.path=path;L.projects=[path,...L.projects.filter(p=>p!==path)];await refreshComputers();}},
199
+ 'add-files':async()=>{if(L.files.length>=5)throw new Error('Add up to five context files.');const paths=await host('choose',{kind:'file',multiple:true,path:L.path});if(paths){if(L.files.length+paths.length>5)throw new Error('Add up to five context files.');for(const path of paths)L.files.push({path,text:await host('text-file',{path})});}},
200
+ 'add-link':async()=>{const link=await host('clipboard');if(!/^https?:\/\//.test(link||''))throw new Error('Copy a web link, then add it as context.');L.link=link;},
201
+ 'add-branch':()=>{L.showBranch=!L.showBranch;},'remove-link':()=>{L.link='';},'remove-branch':()=>{L.branch='';L.showBranch=false;},'close-preview':()=>{L.preview=null;}};
202
+ async function perform(action,b){
203
+ try{
204
+ if(action.startsWith('picker:'))return setPicker(action.slice(7));
205
+ if(action.startsWith('pick:'))return await pick(Number(action.slice(5)));
206
+ if(action.startsWith('remove-image:')){L.images.splice(Number(action.slice(13)),1);L.selectedImage=null;L.preview=null;}
207
+ else if(action.startsWith('preview:')){const i=Number(action.slice(8));L.selectedImage=i;L.preview=L.preview===i?null:i;}
208
+ else if(action.startsWith('remove-file:'))L.files.splice(Number(action.slice(12)),1);
209
+ else if(actions[action])await actions[action](b);
210
+ else return;
211
+ }catch(err){L.error=err.message;}
212
+ render();
213
+ }
214
+ // Native menu items (+) arrive as actions on this page.
215
+ window.addEventListener('sf-action',ev=>{if(model.view==='launcher'&&['add-files','add-link','add-branch'].includes(ev.detail)){ev.stopImmediatePropagation();void perform(ev.detail);}});
216
+ app.addEventListener('click',ev=>{if(model.view!=='launcher')return;const b=ev.target.closest('[data-action]');if(!b)return;ev.stopImmediatePropagation();void perform(b.dataset.action,b);},true);
217
+ registerActions({});
218
+ if(model.view==='launcher')void load();
219
+ }