@vibelord/lanes 1.0.1 → 1.0.3
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/package.json +1 -1
- package/payload/lanes/README.md +37 -7
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +664 -50
- package/payload/lanes/client/ghostty/common.conf +3 -0
- package/payload/lanes/client/ghostty/lanes-palette-light.conf +25 -0
- package/payload/lanes/client/ghostty/lanes-palette.conf +25 -0
- package/payload/lanes/client/ghostty/omarchy.conf +3 -2
- package/payload/lanes/client/lanes-launch +18 -3
- package/payload/lanes/client/lanes-window +17 -1
- package/payload/lanes/container/env.sh +1 -1
- package/payload/lanes/container/hooks/claude-settings.json +10 -0
- package/payload/lanes/container/hooks/codex-hooks.json +22 -0
- package/payload/lanes/container/lane +310 -26
- package/payload/lanes/container/lane-signal +173 -0
- package/payload/lanes/docs/ARCHITECTURE.md +87 -13
- package/payload/lanes/docs/DESIGN.md +182 -4
- package/payload/lanes/docs/INSTALL.md +37 -2
- package/payload/lanes/docs/RUNBOOK.md +458 -39
- package/payload/lanes/docs/SHARING.md +240 -5
- package/payload/lanes/holders/exec.sh +2 -5
- package/payload/lanes/holders.lock +13 -0
- package/payload/lanes/install/enable-studio-host.sh +14 -0
- package/payload/lanes/install/host-install.sh +15 -3
- package/payload/lanes/install/services.py +128 -4
- package/payload/lanes/install.sh +108 -3
- package/payload/lanes/libexec/agent_state.py +32 -0
- package/payload/lanes/libexec/cursor_sync.py +10 -2
- package/payload/lanes/libexec/economy_poll.py +685 -0
- package/payload/lanes/libexec/guard.py +27 -1
- package/payload/lanes/libexec/lanes_lib.py +285 -38
- package/payload/lanes/libexec/notify.py +317 -0
- package/payload/lanes/libexec/registrations.py +1 -1
- package/payload/lanes/libexec/session_actions.py +7 -8
- package/payload/lanes/libexec/sidebar.py +911 -218
- package/payload/lanes/libexec/statusd.py +258 -74
- package/payload/lanes/libexec/studio_host.py +1608 -0
- package/payload/lanes/libexec/tokens.py +38 -7
- package/payload/lanes/libexec/view.sh +13 -3
- package/payload/lanes/systemd/lanes-economy.service +20 -0
- package/payload/lanes/systemd/lanes-notify.service +19 -0
- package/payload/lanes/systemd/lanes-studio-host-enrol.service +11 -0
- package/payload/lanes/systemd/lanes-studio-host-enrol.timer +10 -0
- package/payload/lanes/systemd/lanes-studio-host.service +37 -0
- package/payload/lanes/systemd/lanes-sync.service +3 -0
- package/payload/lanes/web/README.md +83 -4
- package/payload/lanes/web/index.html +2 -1
- package/payload/lanes/web/mobile.css +53 -198
- package/payload/lanes/web/mobile.js +87 -55
- package/payload/lanes/zellij/config.kdl +67 -16
- package/payload/manifest.json +5 -3
- package/payload/lanes/libexec/legacy_sessions.py +0 -130
|
@@ -62,231 +62,100 @@ html.lanes-mobile:root {
|
|
|
62
62
|
--zj-yellow: var(--lanes-tone-attention);
|
|
63
63
|
}
|
|
64
64
|
/* END lanes tokens */
|
|
65
|
-
/*
|
|
66
|
-
|
|
67
|
-
html.lanes-
|
|
68
|
-
--lanes-safe-top: env(safe-area-inset-top, 0px);
|
|
69
|
-
--lanes-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
70
|
-
--lanes-safe-left: env(safe-area-inset-left, 0px);
|
|
71
|
-
--lanes-safe-right: env(safe-area-inset-right, 0px);
|
|
72
|
-
--lanes-head: calc(58px + var(--lanes-safe-top));
|
|
73
|
-
--lanes-dock: calc(66px + var(--lanes-safe-bottom));
|
|
74
|
-
--lanes-keys: 0px;
|
|
75
|
-
--lanes-vh: 100dvh;
|
|
76
|
-
--lanes-vw: 100vw;
|
|
77
|
-
--lanes-offset-top: 0px;
|
|
78
|
-
--lanes-offset-left: 0px;
|
|
79
|
-
-webkit-text-size-adjust: 100%;
|
|
80
|
-
overscroll-behavior: none;
|
|
81
|
-
}
|
|
82
|
-
html.lanes-keyboard {
|
|
83
|
-
--lanes-dock: 48px;
|
|
84
|
-
--lanes-keys: 48px;
|
|
85
|
-
--lanes-safe-bottom: 0px;
|
|
86
|
-
}
|
|
65
|
+
/* Layout; colours only via the tokens above */
|
|
66
|
+
html.lanes-mobile { --lanes-safe-top: env(safe-area-inset-top, 0px); --lanes-safe-bottom: env(safe-area-inset-bottom, 0px); --lanes-safe-left: env(safe-area-inset-left, 0px); --lanes-safe-right: env(safe-area-inset-right, 0px); --lanes-head: calc(58px + var(--lanes-safe-top)); --lanes-dock: calc(66px + var(--lanes-safe-bottom)); --lanes-keys: 0px; --lanes-quick: 0px; --lanes-vh: 100dvh; --lanes-vw: 100vw; --lanes-offset-top: 0px; --lanes-offset-left: 0px; -webkit-text-size-adjust: 100%; overscroll-behavior: none; }
|
|
67
|
+
html.lanes-keyboard { --lanes-dock: 48px; --lanes-keys: 48px; --lanes-safe-bottom: 0px; }
|
|
87
68
|
.lanes-mobile body { overscroll-behavior: none; }
|
|
88
|
-
.lanes-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
.lanes-shell {
|
|
92
|
-
color: var(--lanes-text);
|
|
93
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
94
|
-
-webkit-tap-highlight-color: transparent;
|
|
95
|
-
}
|
|
69
|
+
.lanes-mobile body, .lanes-mobile #terminal { background: var(--lanes-surface) !important; }
|
|
70
|
+
.lanes-shell, .lanes-shell *, .lanes-mobile .security-modal *, .lanes-mobile #zj-mobile-root * { box-sizing: border-box; }
|
|
71
|
+
.lanes-shell { color: var(--lanes-text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; -webkit-tap-highlight-color: transparent; }
|
|
96
72
|
.lanes-shell [hidden], .lanes-shell[hidden] { display: none !important; }
|
|
97
|
-
.lanes-shell button, .lanes-mobile #zj-mobile-root button {
|
|
98
|
-
touch-action: manipulation;
|
|
99
|
-
-webkit-tap-highlight-color: transparent;
|
|
100
|
-
cursor: pointer;
|
|
101
|
-
}
|
|
73
|
+
.lanes-shell button, .lanes-mobile #zj-mobile-root button { touch-action: manipulation; -webkit-tap-highlight-color: transparent; cursor: pointer; }
|
|
102
74
|
.lanes-shell button { font: inherit; color: inherit; }
|
|
103
|
-
.lanes-shell button:focus-visible, .lanes-shell input:focus-visible {
|
|
104
|
-
outline: 2px solid var(--lanes-accent); outline-offset: -3px;
|
|
105
|
-
}
|
|
75
|
+
.lanes-shell button:focus-visible, .lanes-shell input:focus-visible { outline: 2px solid var(--lanes-accent); outline-offset: -3px; }
|
|
106
76
|
.lanes-ready .zj-mobile-topbar { display: none !important; }
|
|
107
|
-
.lanes-ready body.zj-mobile-active #terminal {
|
|
108
|
-
|
|
109
|
-
top: calc(var(--lanes-offset-top) + var(--lanes-head) + 8px);
|
|
110
|
-
left: calc(var(--lanes-offset-left) + var(--lanes-safe-left) + 8px);
|
|
111
|
-
width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right) - 16px);
|
|
112
|
-
height: calc(var(--lanes-vh) - var(--lanes-head) - var(--lanes-dock) - var(--lanes-keys) - 16px);
|
|
113
|
-
margin: 0;
|
|
114
|
-
padding: 0;
|
|
115
|
-
}
|
|
116
|
-
.lanes-header {
|
|
117
|
-
position: fixed; z-index: 510;
|
|
118
|
-
top: var(--lanes-offset-top); left: var(--lanes-offset-left);
|
|
119
|
-
width: var(--lanes-vw); height: var(--lanes-head);
|
|
120
|
-
padding: var(--lanes-safe-top) calc(8px + var(--lanes-safe-right)) 0 calc(12px + var(--lanes-safe-left));
|
|
121
|
-
display: flex; align-items: center; gap: 10px;
|
|
122
|
-
background: var(--lanes-bg); border-bottom: 1px solid var(--lanes-border);
|
|
123
|
-
}
|
|
77
|
+
.lanes-ready body.zj-mobile-active #terminal { position: fixed; top: calc(var(--lanes-offset-top) + var(--lanes-head) + 8px); left: calc(var(--lanes-offset-left) + var(--lanes-safe-left) + 8px); width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right) - 16px); height: calc(var(--lanes-vh) - var(--lanes-head) - var(--lanes-dock) - var(--lanes-keys) - var(--lanes-quick) - 16px); margin: 0; padding: 0; }
|
|
78
|
+
.lanes-header { position: fixed; z-index: 510; top: var(--lanes-offset-top); left: var(--lanes-offset-left); width: var(--lanes-vw); height: var(--lanes-head); padding: var(--lanes-safe-top) calc(8px + var(--lanes-safe-right)) 0 calc(16px + var(--lanes-safe-left)); display: flex; align-items: center; gap: 10px; background: var(--lanes-bg); border-bottom: 1px solid var(--lanes-border); }
|
|
124
79
|
.lanes-brand { height: 34px; width: 34px; flex: 0 0 34px; }
|
|
125
|
-
.lanes-context {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
border: 0; background: none; padding: 0; text-align: left;
|
|
129
|
-
}
|
|
130
|
-
.lanes-context strong { font-size: 16px; letter-spacing: -.25px; line-height: 22px; }
|
|
131
|
-
.lanes-context span { font-size: 12px; color: var(--lanes-muted); line-height: 18px; }
|
|
80
|
+
.lanes-context { display: flex; flex: 1; min-width: 0; height: 54px; flex-direction: column; justify-content: center; align-items: flex-start; border: 0; background: none; padding: 0; text-align: left; }
|
|
81
|
+
.lanes-context strong { font-size: 17px; letter-spacing: -.3px; line-height: 22px; }
|
|
82
|
+
.lanes-context span { font-size: 13px; color: var(--lanes-muted); line-height: 18px; }
|
|
132
83
|
.lanes-context strong, .lanes-context span { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
133
|
-
.lanes-button {
|
|
134
|
-
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
135
|
-
gap: 5px; min-width: 48px; min-height: 48px; padding: 5px;
|
|
136
|
-
border: 0; background: transparent; border-radius: 12px;
|
|
137
|
-
}
|
|
84
|
+
.lanes-button { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; min-width: 48px; min-height: 48px; padding: 5px; border: 0; background: transparent; border-radius: 12px; }
|
|
138
85
|
.lanes-button svg { width: 23px; height: 23px; flex: 0 0 23px; }
|
|
139
|
-
.lanes-button span { font-size:
|
|
86
|
+
.lanes-button span { font-size: 12px; font-weight: 600; letter-spacing: .1px; }
|
|
140
87
|
.lanes-more, .lanes-close { flex: 0 0 48px; }
|
|
141
88
|
.lanes-more span, .lanes-close span { display: none; }
|
|
142
|
-
.lanes-button:active, .lanes-workspace:active { background: var(--lanes-press); }
|
|
143
|
-
.lanes-dock {
|
|
144
|
-
|
|
145
|
-
top: calc(var(--lanes-offset-top) + var(--lanes-vh) - var(--lanes-dock));
|
|
146
|
-
left: var(--lanes-offset-left); width: var(--lanes-vw); height: var(--lanes-dock);
|
|
147
|
-
display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
148
|
-
padding: 4px calc(8px + var(--lanes-safe-right)) var(--lanes-safe-bottom) calc(8px + var(--lanes-safe-left));
|
|
149
|
-
border-top: 1px solid var(--lanes-border); background: var(--lanes-bg);
|
|
150
|
-
}
|
|
151
|
-
.lanes-dock .lanes-button { color: var(--lanes-muted); }
|
|
89
|
+
.lanes-button:active, .lanes-workspace:active, .lanes-quick button:active { background: var(--lanes-press); }
|
|
90
|
+
.lanes-dock { position: fixed; z-index: 510; top: calc(var(--lanes-offset-top) + var(--lanes-vh) - var(--lanes-dock)); left: var(--lanes-offset-left); width: var(--lanes-vw); height: var(--lanes-dock); display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); padding: 4px calc(8px + var(--lanes-safe-right)) var(--lanes-safe-bottom) calc(8px + var(--lanes-safe-left)); border-top: 1px solid var(--lanes-border); background: var(--lanes-bg); }
|
|
91
|
+
.lanes-dock .lanes-button { color: var(--lanes-muted); position: relative; }
|
|
152
92
|
.lanes-dock .lanes-button[aria-pressed="true"] { color: var(--lanes-accent); background: var(--lanes-current); }
|
|
153
|
-
.lanes-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
background: var(--lanes-panel); color: var(--lanes-muted);
|
|
157
|
-
}
|
|
158
|
-
.lanes-input-label {
|
|
159
|
-
position: absolute; inset: 0; pointer-events: none;
|
|
160
|
-
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
|
|
161
|
-
}
|
|
93
|
+
.lanes-button[data-count]::after { content: attr(data-count); position: absolute; top: 2px; right: 8px; display: grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--lanes-tone-attention); color: var(--lanes-on-accent); font-size: 12px; font-weight: 600; }
|
|
94
|
+
.lanes-terminal-input { position: relative; align-self: center; min-width: 0; height: 52px; margin: 0 2px; border: 1px solid var(--lanes-border); border-radius: 12px; background: var(--lanes-panel); color: var(--lanes-muted); }
|
|
95
|
+
.lanes-input-label { position: absolute; inset: 0; pointer-events: none; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; }
|
|
162
96
|
.lanes-input-label svg { width: 23px; height: 23px; }
|
|
163
|
-
.lanes-input-label span { font-size:
|
|
164
|
-
.lanes-terminal-input:focus-within, .lanes-terminal-input.is-typing {
|
|
165
|
-
border-color: var(--lanes-accent); color: var(--lanes-accent); background: var(--lanes-current);
|
|
166
|
-
}
|
|
97
|
+
.lanes-input-label span { font-size: 12px; font-weight: 600; letter-spacing: .1px; }
|
|
98
|
+
.lanes-terminal-input:focus-within, .lanes-terminal-input.is-typing { border-color: var(--lanes-accent); color: var(--lanes-accent); background: var(--lanes-current); }
|
|
167
99
|
.lanes-keyboard .lanes-dock { padding-top: 0; }
|
|
168
100
|
.lanes-keyboard .lanes-dock .lanes-button { min-height: 44px; padding: 0; }
|
|
169
101
|
.lanes-keyboard .lanes-dock .lanes-button span { display: none; }
|
|
170
102
|
.lanes-keyboard .lanes-terminal-input { height: 44px; }
|
|
171
103
|
.lanes-keyboard .lanes-input-label { flex-direction: row; gap: 5px; }
|
|
172
|
-
.lanes-ready .zj-mobile-modbar {
|
|
173
|
-
top: calc(var(--lanes-offset-top) + var(--lanes-vh) - var(--lanes-dock) - var(--lanes-keys));
|
|
174
|
-
bottom: auto; left: calc(var(--lanes-offset-left) + var(--lanes-safe-left));
|
|
175
|
-
right: auto; width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right));
|
|
176
|
-
height: 48px; z-index: 510;
|
|
177
|
-
overflow-x: auto; overscroll-behavior: contain;
|
|
178
|
-
scrollbar-width: none; background: var(--lanes-panel); border-color: var(--lanes-border);
|
|
179
|
-
}
|
|
104
|
+
.lanes-ready .zj-mobile-modbar, .lanes-quick { top: calc(var(--lanes-offset-top) + var(--lanes-vh) - var(--lanes-dock) - var(--lanes-keys)); bottom: auto; left: calc(var(--lanes-offset-left) + var(--lanes-safe-left)); right: auto; width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right)); height: 48px; z-index: 510; overflow-x: auto; overscroll-behavior: contain; scrollbar-width: none; background: var(--lanes-panel); border-color: var(--lanes-border); }
|
|
180
105
|
.lanes-ready:not(.lanes-keyboard) .zj-mobile-modbar { display: none !important; }
|
|
106
|
+
.lanes-quick { position: fixed; top: calc(var(--lanes-offset-top) + var(--lanes-vh) - var(--lanes-dock) - var(--lanes-keys) - var(--lanes-quick)); display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; padding: 2px 16px; border-top: 1px solid var(--lanes-border); }
|
|
107
|
+
.lanes-quick button { min-height: 44px; min-width: 44px; border: 1px solid var(--lanes-border); border-radius: 12px; background: var(--lanes-bg); font: 600 15px ui-monospace, Menlo, monospace; }
|
|
108
|
+
.lanes-quick .is-destructive { color: var(--lanes-tone-urgent); }
|
|
109
|
+
.lanes-quick .is-destructive:active { background: var(--lanes-tone-urgent); color: var(--lanes-on-accent); transition: background 0s 400ms, color 0s 400ms; }
|
|
181
110
|
.lanes-ready.lanes-keyboard .zj-mobile-modbar { display: flex !important; }
|
|
182
|
-
.lanes-ready .zj-mobile-modbar button {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
.lanes-ready .zj-mobile-modbar button.zj-armed { background: var(--lanes-accent); color: #101318; }
|
|
188
|
-
.lanes-sheet {
|
|
189
|
-
position: fixed; z-index: 800;
|
|
190
|
-
top: var(--lanes-offset-top); left: var(--lanes-offset-left);
|
|
191
|
-
height: var(--lanes-vh); width: var(--lanes-vw);
|
|
192
|
-
display: flex; align-items: flex-end; background: #0009;
|
|
193
|
-
padding: var(--lanes-safe-top) var(--lanes-safe-right) 0 var(--lanes-safe-left);
|
|
194
|
-
}
|
|
195
|
-
.lanes-sheet-panel {
|
|
196
|
-
display: flex; flex-direction: column; width: 100%; min-height: 0;
|
|
197
|
-
max-height: calc(var(--lanes-vh) - var(--lanes-safe-top) - 20px);
|
|
198
|
-
background: var(--lanes-bg); border: 1px solid var(--lanes-border);
|
|
199
|
-
border-bottom: 0; border-radius: 24px 24px 0 0;
|
|
200
|
-
padding: 20px 16px calc(8px + var(--lanes-safe-bottom));
|
|
201
|
-
box-shadow: 0 -16px 80px #0007;
|
|
202
|
-
}
|
|
111
|
+
.lanes-ready .zj-mobile-modbar button { flex: 1 0 44px; min-width: 44px; min-height: 48px; color: var(--lanes-text); font: 500 13px -apple-system, sans-serif; border-color: var(--lanes-border); }
|
|
112
|
+
.lanes-ready .zj-mobile-modbar button.zj-armed { background: var(--lanes-accent); color: var(--lanes-on-accent); }
|
|
113
|
+
.lanes-sheet { position: fixed; z-index: 800; top: var(--lanes-offset-top); left: var(--lanes-offset-left); height: var(--lanes-vh); width: var(--lanes-vw); display: flex; align-items: flex-end; background: var(--lanes-scrim); padding: var(--lanes-safe-top) var(--lanes-safe-right) 0 var(--lanes-safe-left); }
|
|
114
|
+
.lanes-sheet-panel { display: flex; flex-direction: column; width: 100%; min-height: 0; max-height: calc(var(--lanes-vh) - var(--lanes-safe-top) - 20px); background: var(--lanes-bg); border: 1px solid var(--lanes-border); border-bottom: 0; border-radius: 24px 24px 0 0; padding: 20px 16px calc(8px + var(--lanes-safe-bottom)); box-shadow: var(--lanes-shadow); }
|
|
203
115
|
.lanes-sheet-head { display: flex; justify-content: space-between; align-items: center; flex: 0 0 auto; margin-bottom: 18px; }
|
|
204
116
|
.lanes-sheet-head h2 { font-size: 25px; line-height: 30px; letter-spacing: -.8px; margin: 0 0 3px; }
|
|
205
117
|
.lanes-sheet-head p { font-size: 13px; color: var(--lanes-muted); margin: 0; }
|
|
206
118
|
.lanes-close { background: var(--lanes-panel); border-radius: 50%; }
|
|
207
|
-
.lanes-search {
|
|
208
|
-
display: flex; align-items: center; gap: 10px; flex: 0 0 48px;
|
|
209
|
-
height: 48px; background: var(--lanes-panel); border: 1px solid var(--lanes-border);
|
|
210
|
-
border-radius: 12px; padding: 0 14px; margin-bottom: 12px;
|
|
211
|
-
}
|
|
119
|
+
.lanes-search { display: flex; align-items: center; gap: 10px; flex: 0 0 48px; height: 48px; background: var(--lanes-panel); border: 1px solid var(--lanes-border); border-radius: 12px; padding: 0 14px; margin-bottom: 12px; }
|
|
212
120
|
.lanes-search svg { width: 20px; height: 20px; color: var(--lanes-muted); flex-shrink: 0; }
|
|
213
|
-
.lanes-search input {
|
|
214
|
-
border: 0; outline: none; background: transparent; color: var(--lanes-text);
|
|
215
|
-
font: 16px -apple-system, sans-serif; min-width: 0; width: 100%; height: 44px;
|
|
216
|
-
}
|
|
121
|
+
.lanes-search input { border: 0; outline: none; background: transparent; color: var(--lanes-text); font: 16px -apple-system, sans-serif; min-width: 0; width: 100%; height: 44px; }
|
|
217
122
|
.lanes-search input::placeholder { color: var(--lanes-muted); }
|
|
218
|
-
.lanes-workspaces {
|
|
219
|
-
|
|
220
|
-
display: flex; flex-direction: column; gap: 6px;
|
|
221
|
-
-webkit-overflow-scrolling: touch;
|
|
222
|
-
}
|
|
223
|
-
.lanes-workspace {
|
|
224
|
-
display: flex; align-items: center; gap: 12px;
|
|
225
|
-
flex: 0 0 auto; width: 100%; min-height: 74px; padding: 12px;
|
|
226
|
-
background: var(--lanes-panel); border: 1px solid transparent; border-radius: 14px;
|
|
227
|
-
text-align: left;
|
|
228
|
-
}
|
|
123
|
+
.lanes-workspaces { min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 8px; -webkit-overflow-scrolling: touch; }
|
|
124
|
+
.lanes-workspace { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; width: 100%; min-height: 74px; padding: 12px; background: var(--lanes-panel); border: 1px solid transparent; border-radius: 14px; text-align: left; }
|
|
229
125
|
.lanes-workspace.is-current { border-color: var(--lanes-current-border); background: var(--lanes-current); }
|
|
230
|
-
.lanes-monogram {
|
|
231
|
-
display: grid; place-items: center; width: 40px; height: 40px; flex: 0 0 40px;
|
|
232
|
-
font-size: 17px; font-weight: 650; color: var(--lanes-accent);
|
|
233
|
-
background: var(--lanes-chip); border: 1px solid var(--lanes-border); border-radius: 11px;
|
|
234
|
-
}
|
|
126
|
+
.lanes-monogram { display: grid; place-items: center; width: 40px; height: 40px; flex: 0 0 40px; font-size: 17px; font-weight: 650; color: var(--lanes-accent); background: var(--lanes-chip); border: 1px solid var(--lanes-border); border-radius: 11px; }
|
|
235
127
|
.lanes-workspace-copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
|
|
236
128
|
.lanes-workspace-copy strong { font-size: 16px; font-weight: 600; line-height: 21px; overflow-wrap: anywhere; }
|
|
237
|
-
.lanes-workspace-copy small { font-size:
|
|
129
|
+
.lanes-workspace-copy small { font-size: 13px; color: var(--lanes-muted); }
|
|
130
|
+
.lanes-tone-urgent { color: var(--lanes-tone-urgent); }
|
|
131
|
+
.lanes-tone-attention { color: var(--lanes-tone-attention); }
|
|
132
|
+
.lanes-tone-working { color: var(--lanes-tone-working); }
|
|
133
|
+
.lanes-tone-done { color: var(--lanes-tone-done); }
|
|
238
134
|
.lanes-workspace-arrow { width: 18px; flex: 0 0 18px; color: var(--lanes-muted); }
|
|
239
135
|
.is-current .lanes-workspace-arrow { color: var(--lanes-accent); }
|
|
240
136
|
.lanes-session-link { border: 0; background: transparent; min-height: 48px; color: var(--lanes-muted) !important; font-size: 13px !important; flex-shrink: 0; }
|
|
241
137
|
.lanes-empty { text-align: center; color: var(--lanes-muted); padding: 30px 12px; }
|
|
242
|
-
.lanes-mobile #zj-mobile-root { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
|
243
|
-
.lanes-mobile .zj-mobile-menu {
|
|
244
|
-
top: calc(var(--lanes-offset-top) + var(--lanes-head) + 6px);
|
|
245
|
-
right: calc(var(--lanes-safe-right) + 8px); left: auto;
|
|
246
|
-
max-height: calc(var(--lanes-vh) - var(--lanes-head) - 16px);
|
|
247
|
-
max-width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right) - 16px);
|
|
248
|
-
min-width: min(290px, 90vw); overflow-y: auto;
|
|
249
|
-
background: var(--lanes-panel); border: 1px solid var(--lanes-border);
|
|
250
|
-
border-radius: 16px; box-shadow: 0 12px 48px #0009;
|
|
251
|
-
}
|
|
138
|
+
.lanes-mobile #zj-mobile-root, .lanes-mobile .security-modal { font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
|
|
139
|
+
.lanes-mobile .zj-mobile-menu { top: calc(var(--lanes-offset-top) + var(--lanes-head) + 6px); right: calc(var(--lanes-safe-right) + 8px); left: auto; max-height: calc(var(--lanes-vh) - var(--lanes-head) - 16px); max-width: calc(var(--lanes-vw) - var(--lanes-safe-left) - var(--lanes-safe-right) - 16px); min-width: min(290px, 90vw); overflow-y: auto; background: var(--lanes-panel); border: 1px solid var(--lanes-border); border-radius: 16px; box-shadow: var(--lanes-shadow); }
|
|
252
140
|
.lanes-mobile .zj-mobile-menu button { min-height: 48px; color: var(--lanes-text); font-size: 15px; }
|
|
253
141
|
.lanes-font-controls { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border-bottom: 1px solid var(--lanes-border); }
|
|
254
142
|
.lanes-font-controls > span { margin-right: auto; font-size: 13px; color: var(--lanes-muted); }
|
|
255
143
|
.lanes-font-controls button { min-width: 44px; padding: 8px !important; text-align: center !important; }
|
|
256
144
|
.lanes-font-controls output { font: 12px -apple-system, sans-serif; width: 36px; text-align: center; color: var(--lanes-muted); }
|
|
257
|
-
.lanes-mobile .zj-mobile-overlay {
|
|
258
|
-
top: var(--lanes-offset-top); left: var(--lanes-offset-left); right: auto;
|
|
259
|
-
width: var(--lanes-vw); height: var(--lanes-vh); max-height: var(--lanes-vh);
|
|
260
|
-
padding: var(--lanes-safe-top) var(--lanes-safe-right) var(--lanes-safe-bottom) var(--lanes-safe-left);
|
|
261
|
-
background: var(--lanes-bg); color: var(--lanes-text);
|
|
262
|
-
}
|
|
145
|
+
.lanes-mobile .zj-mobile-overlay { top: var(--lanes-offset-top); left: var(--lanes-offset-left); right: auto; width: var(--lanes-vw); height: var(--lanes-vh); max-height: var(--lanes-vh); padding: var(--lanes-safe-top) var(--lanes-safe-right) var(--lanes-safe-bottom) var(--lanes-safe-left); background: var(--lanes-bg); color: var(--lanes-text); }
|
|
263
146
|
.lanes-mobile .zj-mobile-overlay-header { border-color: var(--lanes-border); padding: 8px 12px; min-height: 64px; }
|
|
264
147
|
.lanes-mobile .zj-mobile-overlay-header .zj-back { min-width: 64px; min-height: 48px; border-radius: 12px; border-color: var(--lanes-border); }
|
|
265
148
|
.lanes-mobile .zj-mobile-overlay-header .zj-title { font-size: 19px; font-weight: 600; }
|
|
266
149
|
.lanes-mobile .zj-mobile-search-row { padding: 12px; }
|
|
267
|
-
.lanes-mobile .zj-mobile-overlay input.zj-search, .lanes-mobile .zj-mobile-overlay input.zj-name {
|
|
268
|
-
min-width: 0; font-size: 16px; min-height: 48px; border-radius: 10px; padding: 10px;
|
|
269
|
-
color: var(--lanes-text); background: var(--lanes-panel); border-color: var(--lanes-border);
|
|
270
|
-
}
|
|
150
|
+
.lanes-mobile .zj-mobile-overlay input.zj-search, .lanes-mobile .zj-mobile-overlay input.zj-name { min-width: 0; font-size: 16px; min-height: 48px; border-radius: 10px; padding: 10px; color: var(--lanes-text); background: var(--lanes-panel); border-color: var(--lanes-border); }
|
|
271
151
|
.lanes-mobile .zj-mobile-cards { padding: 0 12px 12px; overscroll-behavior: contain; }
|
|
272
152
|
.lanes-mobile .zj-mobile-card { min-height: 72px; padding: 16px; border: 1px solid var(--lanes-border); border-radius: 14px; background: var(--lanes-panel); }
|
|
273
153
|
.lanes-mobile .zj-mobile-card .zj-card-title { color: var(--lanes-accent); font-size: 16px; overflow-wrap: anywhere; }
|
|
274
|
-
.lanes-mobile .zj-mobile-card .zj-card-meta { color: var(--lanes-muted); font-size:
|
|
154
|
+
.lanes-mobile .zj-mobile-card .zj-card-meta { color: var(--lanes-muted); font-size: 13px; overflow-wrap: anywhere; }
|
|
275
155
|
.lanes-mobile .zj-mobile-footer { padding-bottom: 8px; }
|
|
276
156
|
.lanes-mobile .zj-mobile-footer button { min-height: 48px; border-radius: 12px; color: var(--lanes-accent); border-color: var(--lanes-border); }
|
|
277
|
-
.lanes-mobile .security-modal {
|
|
278
|
-
|
|
279
|
-
display: flex; align-items: center; justify-content: center;
|
|
280
|
-
top: var(--lanes-offset-top); left: var(--lanes-offset-left);
|
|
281
|
-
width: var(--lanes-vw); height: var(--lanes-vh);
|
|
282
|
-
padding: calc(16px + var(--lanes-safe-top)) calc(20px + var(--lanes-safe-right)) calc(16px + var(--lanes-safe-bottom)) calc(20px + var(--lanes-safe-left));
|
|
283
|
-
overflow: auto; background: var(--lanes-bg); font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
284
|
-
}
|
|
285
|
-
.lanes-mobile .security-modal .security-modal-content {
|
|
286
|
-
width: 100%; max-width: 400px; max-height: 100%; overflow-y: auto;
|
|
287
|
-
border: 1px solid var(--lanes-border); border-radius: 22px;
|
|
288
|
-
padding: 24px; background: var(--lanes-panel); color: var(--lanes-text); box-shadow: none;
|
|
289
|
-
}
|
|
157
|
+
.lanes-mobile .security-modal { box-sizing: border-box; top: var(--lanes-offset-top); left: var(--lanes-offset-left); width: var(--lanes-vw); height: var(--lanes-vh); padding: calc(16px + var(--lanes-safe-top)) calc(20px + var(--lanes-safe-right)) calc(16px + var(--lanes-safe-bottom)) calc(20px + var(--lanes-safe-left)); overflow: auto; background: var(--lanes-bg); }
|
|
158
|
+
.lanes-mobile .security-modal .security-modal-content { width: 100%; max-width: 400px; max-height: 100%; overflow-y: auto; border: 1px solid var(--lanes-border); border-radius: 24px; padding: 24px; background: var(--lanes-panel); color: var(--lanes-text); box-shadow: none; }
|
|
290
159
|
.lanes-mobile .security-modal .security-modal-content::before { display: none; }
|
|
291
160
|
.lanes-mobile .security-modal h3 { font-size: 25px; font-weight: 650; text-transform: none; letter-spacing: -.7px; color: var(--lanes-text); border: 0; margin-bottom: 18px; }
|
|
292
161
|
.lanes-mobile .security-modal input[type="password"] { display: block; width: 100%; padding: 12px; margin-bottom: 12px; font-size: 16px; min-height: 52px; border-radius: 12px; background: var(--lanes-bg); color: var(--lanes-text); border: 1px solid var(--lanes-border); appearance: none; }
|
|
@@ -294,18 +163,15 @@ html.lanes-keyboard {
|
|
|
294
163
|
.lanes-mobile .security-modal input[type="checkbox"] { width: 22px; height: 22px; border-radius: 6px; flex: 0 0 22px; accent-color: var(--lanes-accent); }
|
|
295
164
|
.lanes-mobile .security-modal .button-row { display: flex; gap: 12px; margin-top: 20px; }
|
|
296
165
|
.lanes-mobile .security-modal button { min-height: 48px; border: 1px solid; border-radius: 12px; font-size: 15px; padding: 10px 20px; appearance: none; }
|
|
297
|
-
.lanes-mobile .security-modal .submit-btn { background: var(--lanes-accent); border-color: var(--lanes-accent); color:
|
|
166
|
+
.lanes-mobile .security-modal .submit-btn { background: var(--lanes-accent); border-color: var(--lanes-accent); color: var(--lanes-on-accent); font-weight: 650; flex: 1; }
|
|
298
167
|
.lanes-mobile .security-modal .cancel-btn { background: transparent; color: var(--lanes-muted); border-color: var(--lanes-border); }
|
|
299
168
|
@media (orientation: landscape) and (max-height: 500px) {
|
|
300
169
|
html.lanes-mobile { --lanes-head: calc(48px + var(--lanes-safe-top)); --lanes-dock: calc(48px + var(--lanes-safe-bottom)); }
|
|
301
|
-
html.lanes-keyboard { --lanes-dock: 48px; }
|
|
302
170
|
.lanes-context { height: 46px; }
|
|
303
171
|
.lanes-dock { padding-top: 0; }
|
|
304
172
|
.lanes-dock .lanes-button { flex-direction: row; gap: 8px; min-height: 44px; }
|
|
305
|
-
.lanes-dock .lanes-button span { font-size: 12px; }
|
|
306
173
|
.lanes-terminal-input { height: 44px; }
|
|
307
174
|
.lanes-input-label { flex-direction: row; gap: 8px; }
|
|
308
|
-
.lanes-input-label span { font-size: 12px; }
|
|
309
175
|
.lanes-sheet-panel { padding-top: 12px; }
|
|
310
176
|
.lanes-sheet-head { margin-bottom: 8px; }
|
|
311
177
|
.lanes-workspaces { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
@@ -314,20 +180,9 @@ html.lanes-keyboard {
|
|
|
314
180
|
.lanes-sheet-panel { animation: lanes-sheet-in 150ms ease-out; }
|
|
315
181
|
@keyframes lanes-sheet-in { from {transform: translateY(20px); opacity: .4;} to {transform: translateY(0); opacity: 1;} }
|
|
316
182
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
.lanes-filter-bar {
|
|
320
|
-
display: flex; align-items: center; gap: 8px;
|
|
321
|
-
flex: 1 1 auto; min-width: 0;
|
|
322
|
-
padding: 0 12px; height: 44px;
|
|
323
|
-
background: var(--lanes-chip); border: 1px solid var(--lanes-border); border-radius: 12px;
|
|
324
|
-
}
|
|
325
|
-
.lanes-filter-bar[hidden] { display: none; }
|
|
183
|
+
/* Lane filter bar (☰ picker tab) */
|
|
184
|
+
.lanes-filter-bar { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; padding: 0 12px; height: 44px; background: var(--lanes-chip); border: 1px solid var(--lanes-border); border-radius: 12px; }
|
|
326
185
|
.lanes-filter-bar > svg { width: 18px; height: 18px; flex: 0 0 18px; color: var(--lanes-muted); }
|
|
327
|
-
.lanes-filter-bar input {
|
|
328
|
-
flex: 1 1 auto; min-width: 0;
|
|
329
|
-
background: transparent; border: 0; outline: none;
|
|
330
|
-
color: var(--lanes-text); font-size: 16px; caret-color: var(--lanes-accent);
|
|
331
|
-
}
|
|
186
|
+
.lanes-filter-bar input { flex: 1 1 auto; min-width: 0; background: transparent; border: 0; outline: none; color: var(--lanes-text); font-size: 16px; caret-color: var(--lanes-accent); }
|
|
332
187
|
.lanes-filter-bar input::placeholder { color: var(--lanes-muted); }
|
|
333
188
|
.lanes-filter-bar input::-webkit-search-cancel-button { -webkit-appearance: none; }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/* Lanes browser shell
|
|
2
|
-
* stay upstream. LNS-31: phone (today's rule or a coarse landscape phone), tablet (other touch), desktop. */
|
|
1
|
+
/* Lanes browser shell; see web/README.md, Code notes */
|
|
3
2
|
function laneShellMode({userAgent, coarse, width, height} = {}) {
|
|
4
3
|
if (/iPhone|iPod|Android.*Mobile/i.test(userAgent) || (coarse && (width <= 600 || height <= 500))) return 'phone';
|
|
5
4
|
return coarse ? 'tablet' : 'desktop';
|
|
@@ -11,8 +10,11 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
11
10
|
const mode = laneShellMode({userAgent: navigator.userAgent, coarse: matchMedia('(pointer: coarse)').matches,
|
|
12
11
|
width: innerWidth, height: innerHeight});
|
|
13
12
|
const root = document.documentElement;
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// BEGIN lanes tokens (generated by libexec/tokens.py --js; do not edit by hand)
|
|
14
|
+
const LANES_ANSI = {dark: ['#7d8896', '#f28b7d', '#6fd39a', '#e5b84a', '#7fb3f2', '#c9a6ff', '#6fd6d0', '#9ca7b5', '#8a95a3', '#ff9d96', '#8fe0a3', '#f7cf7f', '#a5cbff', '#dcc6ff', '#9ae6e0', '#edf1f5'], light: ['#3b4a42', '#b7322a', '#1b7142', '#8a5a00', '#2456a8', '#7a3fb8', '#0f7b7b', '#56616d', '#5f6d66', '#c2332a', '#1b7a38', '#8f5a00', '#2860c4', '#8a4dc8', '#0f7272', '#17241b']};
|
|
15
|
+
// END lanes tokens
|
|
16
|
+
root.dataset.lanesShell = mode;
|
|
17
|
+
if (mode !== 'phone') {
|
|
16
18
|
const add = (tag, props) => document.head.append(Object.assign(document.createElement(tag), props));
|
|
17
19
|
add('link', {rel: 'stylesheet', href: '/lanes-assets/browser.css'});
|
|
18
20
|
add('script', {src: '/lanes-assets/browser.js'});
|
|
@@ -20,14 +22,10 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
20
22
|
if (mode === 'desktop') return;
|
|
21
23
|
root.classList.add('lanes-mobile');
|
|
22
24
|
if (mode === 'tablet') {
|
|
23
|
-
// iPad Safari's UA is desktop: ask for the mobile UI (a stored 'off' stays).
|
|
24
25
|
root.classList.add('lanes-tablet');
|
|
25
26
|
try {localStorage.getItem('zellij:mobile-ui') || localStorage.setItem('zellij:mobile-ui', 'on');} catch (_) {}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
// Only a browser's native tap/focus on an input may summon the keyboard. Zellij
|
|
29
|
-
// otherwise refocuses a hidden input on touchstart, touchend, click and server
|
|
30
|
-
// SetSoftKeyboard messages. Install before its first terminal or login modal.
|
|
31
29
|
const elementFocus = HTMLElement.prototype.focus;
|
|
32
30
|
HTMLElement.prototype.focus = function(...args) {
|
|
33
31
|
const textInput = this instanceof HTMLInputElement &&
|
|
@@ -35,11 +33,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
35
33
|
if (textInput || this instanceof HTMLTextAreaElement || this.isContentEditable) return;
|
|
36
34
|
return elementFocus.apply(this, args);
|
|
37
35
|
};
|
|
38
|
-
Object.defineProperty(window, '__zjSoftKbdEnabled', {
|
|
39
|
-
configurable: false,
|
|
40
|
-
get: () => false,
|
|
41
|
-
set: () => {},
|
|
42
|
-
});
|
|
36
|
+
Object.defineProperty(window, '__zjSoftKbdEnabled', {configurable: false, get: () => false, set: () => {}});
|
|
43
37
|
try {
|
|
44
38
|
const theme = localStorage.getItem('zellij:mobile-theme') || 'dark';
|
|
45
39
|
localStorage.setItem('zellij:mobile-theme', theme);
|
|
@@ -73,7 +67,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
73
67
|
el.addEventListener('click', action);
|
|
74
68
|
return el;
|
|
75
69
|
};
|
|
76
|
-
let shell, heading, subtitle, dock, sheet, list, search, count, lanesButton, typingField;
|
|
70
|
+
let shell, heading, subtitle, dock, sheet, list, search, count, lanesButton, typingField, quickRow, quickOn;
|
|
77
71
|
let laneFilterBar, laneFilterInput, laneFilterPrev = '';
|
|
78
72
|
let api, term, previousCards = '', sheetOpen = false, returnFocus;
|
|
79
73
|
let fontSize = 16;
|
|
@@ -92,9 +86,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
92
86
|
root.style.setProperty('--lanes-vw', `${vv?.width ?? innerWidth}px`);
|
|
93
87
|
root.style.setProperty('--lanes-offset-top', `${vv?.offsetTop ?? 0}px`);
|
|
94
88
|
root.style.setProperty('--lanes-offset-left', `${vv?.offsetLeft ?? 0}px`);
|
|
95
|
-
// Browser toolbars alone must not put the UI into keyboard mode.
|
|
96
89
|
const occluded = !!vv && innerHeight - vv.height - vv.offsetTop > 120;
|
|
97
|
-
// Zellij's keyboard input lives in a closed shadow root, so activeElement is its host.
|
|
98
90
|
const focused = window.__zjSoftKbdCapture?.isFocused || document.activeElement?.matches('input, textarea');
|
|
99
91
|
setKeyboard(occluded && focused);
|
|
100
92
|
}
|
|
@@ -114,19 +106,14 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
114
106
|
label.innerHTML = svg('keyboard');
|
|
115
107
|
label.append(make('span', '', 'Type here'));
|
|
116
108
|
label.setAttribute('aria-hidden', 'true');
|
|
117
|
-
// Keep the original input and its event handlers inside its closed shadow
|
|
118
|
-
// root (including its protection from password-manager extensions). Make it
|
|
119
|
-
// a real, tappable field instead of a 1px invisible focus target. Keystrokes
|
|
120
|
-
// appear in the terminal; its internal padding/history stays invisible.
|
|
121
109
|
host.removeAttribute('aria-hidden');
|
|
122
110
|
host.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;opacity:1;pointer-events:auto;';
|
|
123
111
|
capture.removeAttribute('aria-hidden');
|
|
124
112
|
capture.setAttribute('aria-label', 'Type in terminal');
|
|
125
113
|
capture.tabIndex = 0;
|
|
126
|
-
capture.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;box-sizing:border-box;' +
|
|
127
|
-
'
|
|
128
|
-
'
|
|
129
|
-
'pointer-events:auto;user-select:text;-webkit-user-select:text;';
|
|
114
|
+
capture.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;box-sizing:border-box;font-size:16px;' +
|
|
115
|
+
'border:0;border-radius:12px;padding:0;margin:0;opacity:1;background:transparent;color:transparent;' +
|
|
116
|
+
'caret-color:transparent;outline:none;pointer-events:auto;user-select:text;-webkit-user-select:text;';
|
|
130
117
|
field.append(label, host);
|
|
131
118
|
capture.addEventListener('focus', () => field.classList.add('is-typing'));
|
|
132
119
|
capture.addEventListener('blur', () => field.classList.remove('is-typing'));
|
|
@@ -141,6 +128,20 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
141
128
|
function currentTab() {
|
|
142
129
|
return data()?.tabs.find(t => t.position === data()?.active_pane?.tab_position);
|
|
143
130
|
}
|
|
131
|
+
const GLYPHS = '◆◇▸✓', TONES = ['urgent', 'attention', 'working', 'done'];
|
|
132
|
+
function parseCounts(title) {
|
|
133
|
+
const c = {approval: 0, input: 0, working: 0, done: 0}, keys = Object.keys(c);
|
|
134
|
+
let found = 0;
|
|
135
|
+
for (const [, g, n] of String(title || '').matchAll(/([◆◇▸✓])(\d+)/g)) { c[keys[GLYPHS.indexOf(g)]] = +n; found = 1; }
|
|
136
|
+
return found ? c : null;
|
|
137
|
+
}
|
|
138
|
+
window.__lanesParseCounts = parseCounts;
|
|
139
|
+
const countParts = c => c ? Object.values(c).map((n, i) => [GLYPHS[i], n, TONES[i]]).filter(p => p[1]) : [];
|
|
140
|
+
const pickerTab = name => data()?.tabs.find(t => t.name === `${name} ☰`);
|
|
141
|
+
function pickerTitle(name) {
|
|
142
|
+
const picker = pickerTab(name);
|
|
143
|
+
return picker ? data()?.panes.find(p => p.tab_position === picker.position && !p.is_plugin)?.title || '' : '';
|
|
144
|
+
}
|
|
144
145
|
function workspaces() {
|
|
145
146
|
const tabs = data()?.tabs || [];
|
|
146
147
|
const pickers = tabs.filter(t => t.name.endsWith(' ☰'));
|
|
@@ -176,12 +177,14 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
176
177
|
const items = workspaces();
|
|
177
178
|
const activeName = (currentTab()?.name || '').replace(/ ☰$/, '');
|
|
178
179
|
const query = search.value.trim().toLocaleLowerCase();
|
|
179
|
-
const
|
|
180
|
+
const titles = items.map(w => w.picker ? pickerTitle(w.name) : '');
|
|
181
|
+
const signature = JSON.stringify([items, titles, activeName, query]);
|
|
180
182
|
if (signature === previousCards) return;
|
|
181
183
|
previousCards = signature;
|
|
182
184
|
list.replaceChildren();
|
|
183
185
|
text(count, `${items.length} workspaces`);
|
|
184
|
-
for (const item of items.
|
|
186
|
+
for (const [index, item] of items.entries()) {
|
|
187
|
+
if (!item.name.toLocaleLowerCase().includes(query)) continue;
|
|
185
188
|
const active = item.name === activeName;
|
|
186
189
|
const card = make('button', `lanes-workspace${active ? ' is-current' : ''}`);
|
|
187
190
|
card.type = 'button';
|
|
@@ -189,7 +192,10 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
189
192
|
if (active) card.setAttribute('aria-current', 'true');
|
|
190
193
|
const monogram = make('span', 'lanes-monogram', Array.from(item.name)[0]?.toUpperCase() || 'L');
|
|
191
194
|
const copy = make('span', 'lanes-workspace-copy');
|
|
192
|
-
|
|
195
|
+
const small = make('small'), parts = countParts(parseCounts(titles[index]));
|
|
196
|
+
parts.forEach(([g, n, tone], i) => small.append(i ? ' · ' : '', make('span', `lanes-tone-${tone}`, `${g} ${n}`)));
|
|
197
|
+
if (!parts.length) small.textContent = item.picker ? 'Choose a lane' : 'Open workspace';
|
|
198
|
+
copy.append(make('strong', '', item.name), small);
|
|
193
199
|
const arrow = make('span', 'lanes-workspace-arrow');
|
|
194
200
|
arrow.innerHTML = svg(active ? 'check' : 'arrow');
|
|
195
201
|
card.append(monogram, copy, arrow);
|
|
@@ -214,8 +220,7 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
214
220
|
dock = make('nav', 'lanes-dock'); dock.setAttribute('aria-label', 'Workspace controls');
|
|
215
221
|
const spaces = button('Workspaces', 'workspaces', openSheet);
|
|
216
222
|
lanesButton = button('Lanes', 'lanes', () => {
|
|
217
|
-
const
|
|
218
|
-
const picker = data()?.tabs.find(t => t.name === `${name} ☰`);
|
|
223
|
+
const picker = pickerTab((currentTab()?.name || '').replace(/ ☰$/, ''));
|
|
219
224
|
if (picker) focusTab(picker); else openSheet();
|
|
220
225
|
});
|
|
221
226
|
const panes = button('Panes', 'panes', () => nativeClick('.zj-pane'));
|
|
@@ -250,12 +255,10 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
250
255
|
if (ev.shiftKey && document.activeElement === first) {ev.preventDefault(); last.focus();}
|
|
251
256
|
else if (!ev.shiftKey && document.activeElement === last) {ev.preventDefault(); first.focus();}
|
|
252
257
|
});
|
|
253
|
-
|
|
258
|
+
quickRow = buildQuickRow();
|
|
259
|
+
shell.append(header, quickRow, dock, sheet); document.body.append(shell);
|
|
254
260
|
if (mode === 'tablet') more.before(dock);
|
|
255
261
|
root.classList.add('lanes-ready');
|
|
256
|
-
// Upstream cancels pointerdown on extra keys, which also suppresses their
|
|
257
|
-
// touch click in Safari. Cancel mousedown instead: keep typing focus while
|
|
258
|
-
// allowing the native click to activate CTRL, arrows, and the other keys.
|
|
259
262
|
for (const key of document.querySelectorAll('.zj-mobile-modbar button')) {
|
|
260
263
|
key.addEventListener('pointerdown', ev => ev.stopImmediatePropagation(), true);
|
|
261
264
|
key.addEventListener('mousedown', ev => ev.preventDefault());
|
|
@@ -264,31 +267,39 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
264
267
|
resize();
|
|
265
268
|
}
|
|
266
269
|
function sendToTerminal(bytes) {
|
|
267
|
-
|
|
268
|
-
// then fall back to the keyboard capture element.
|
|
269
|
-
if (window.__zjSendControl) {
|
|
270
|
-
try { window.__zjSendControl({type: 'Write', bytes}); return; } catch (_) {}
|
|
271
|
-
}
|
|
272
|
-
const el = window.__zjSoftKbdCapture?.element;
|
|
273
|
-
if (!el) return;
|
|
274
|
-
const text = String.fromCharCode(...bytes);
|
|
275
|
-
el.focus({preventScroll: true});
|
|
276
|
-
try { document.execCommand('insertText', false, text); } catch (_) {}
|
|
270
|
+
try { window.__zjSendControl?.({type: 'Write', bytes}); } catch (_) {}
|
|
277
271
|
}
|
|
278
272
|
function applyLaneFilter(query) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
const bytes = [...new TextEncoder().encode(query)].filter(b => b >= 0x20 && b !== 0x7f);
|
|
274
|
+
sendToTerminal([0x1b, 0x2f, ...bytes]);
|
|
275
|
+
}
|
|
276
|
+
function buildQuickRow() {
|
|
277
|
+
const row = make('div', 'lanes-quick');
|
|
278
|
+
row.hidden = true;
|
|
279
|
+
for (const [label, bytes, name, hold] of [['⏎', [13], 'Enter'], ['y', [121]], ['n', [110], 0, 1], ['Esc', [27]],
|
|
280
|
+
['^C', [3], 'Control C', 1], ['↑', [27, 91, 65], 'Up'], ['↓', [27, 91, 66], 'Down']]) {
|
|
281
|
+
const key = make('button', hold ? 'is-destructive' : '', label);
|
|
282
|
+
let down = -1;
|
|
283
|
+
key.type = 'button'; if (name) key.setAttribute('aria-label', name);
|
|
284
|
+
key.addEventListener('mousedown', ev => ev.preventDefault());
|
|
285
|
+
key.addEventListener('pointerdown', ev => { down = ev.timeStamp; });
|
|
286
|
+
key.addEventListener('pointerup', ev => {
|
|
287
|
+
if (!hold || (down >= 0 && ev.timeStamp - down >= 400)) sendToTerminal(bytes); else text(subtitle, 'Hold to send');
|
|
288
|
+
down = -1;
|
|
289
|
+
});
|
|
290
|
+
row.append(key);
|
|
291
|
+
}
|
|
292
|
+
return row;
|
|
282
293
|
}
|
|
283
294
|
function clearLaneFilter() {
|
|
284
|
-
sendToTerminal([0x1b]);
|
|
295
|
+
sendToTerminal([0x1b]);
|
|
285
296
|
laneFilterPrev = '';
|
|
286
297
|
if (laneFilterInput) laneFilterInput.value = '';
|
|
287
298
|
}
|
|
288
299
|
function buildLaneFilterBar() {
|
|
289
300
|
const bar = make('div', 'lanes-filter-bar');
|
|
290
301
|
bar.setAttribute('aria-label', 'Filter lanes');
|
|
291
|
-
bar.
|
|
302
|
+
bar.hidden = true;
|
|
292
303
|
bar.innerHTML = svg('search');
|
|
293
304
|
laneFilterInput = make('input');
|
|
294
305
|
laneFilterInput.type = 'search'; laneFilterInput.placeholder = 'Filter lanes';
|
|
@@ -317,6 +328,17 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
317
328
|
text(document.getElementById('lanes-font-value'), `${fontSize} px`);
|
|
318
329
|
resize();
|
|
319
330
|
}
|
|
331
|
+
function lanesTheme() {
|
|
332
|
+
const css = getComputedStyle(root), v = p => css.getPropertyValue(p).trim();
|
|
333
|
+
const t = {background: v('--lanes-surface'), foreground: v('--lanes-ink'), cursor: v('--lanes-accent'),
|
|
334
|
+
selectionBackground: v('--lanes-accent-wash'), selectionForeground: v('--lanes-ink')};
|
|
335
|
+
LANES_ANSI[root.dataset.zjTheme === 'light' ? 'light' : 'dark'].forEach((c, i) => {
|
|
336
|
+
const k = 'Black Red Green Yellow Blue Magenta Cyan White'.split(' ')[i % 8];
|
|
337
|
+
t[i < 8 ? k.toLowerCase() : 'bright' + k] = c;
|
|
338
|
+
});
|
|
339
|
+
return t;
|
|
340
|
+
}
|
|
341
|
+
const applyTheme = () => { if (term) term.options.theme = lanesTheme(); };
|
|
320
342
|
function installFontControls() {
|
|
321
343
|
const row = make('div', 'lanes-font-controls');
|
|
322
344
|
row.append(make('span', '', 'Text size'));
|
|
@@ -338,20 +360,32 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
338
360
|
const name = tab?.name.replace(/ ☰$/, '') || 'Lanes';
|
|
339
361
|
const picker = tab?.name.endsWith(' ☰');
|
|
340
362
|
const pane = data()?.panes.find(p => p.pane_id === data()?.active_pane?.pane_id && !p.is_plugin);
|
|
363
|
+
const counts = parseCounts(pickerTitle(name));
|
|
364
|
+
const chips = countParts(counts).map(([glyph, n]) => glyph + n).join(' ');
|
|
341
365
|
text(heading, name);
|
|
342
|
-
text(subtitle, picker ? 'Choose a lane to continue' : pane?.title || 'Your workspace');
|
|
366
|
+
text(subtitle, picker ? chips || 'Choose a lane to continue' : pane?.title || 'Your workspace');
|
|
343
367
|
lanesButton.setAttribute('aria-pressed', String(!!picker));
|
|
368
|
+
const needs = counts ? counts.approval + counts.input : 0;
|
|
369
|
+
if (needs) lanesButton.dataset.count = String(needs); else delete lanesButton.dataset.count;
|
|
344
370
|
updateLaneFilterBar(picker);
|
|
345
|
-
|
|
371
|
+
const quick = active && !picker;
|
|
372
|
+
if (quick !== quickOn) {
|
|
373
|
+
quickOn = quick;
|
|
374
|
+
quickRow.hidden = !quick;
|
|
375
|
+
root.style.setProperty('--lanes-quick', quick ? '48px' : '0px');
|
|
376
|
+
resize();
|
|
377
|
+
}
|
|
378
|
+
document.title = `${chips ? chips + ' · ' : ''}${name} · Lanes`;
|
|
346
379
|
renderCards();
|
|
347
380
|
}
|
|
348
381
|
function connect() {
|
|
349
382
|
if (api || !window.__zjMobileUi || !window.term || !window.__zjSoftKbdCapture?.element) return;
|
|
350
|
-
// Wait for server state: resizing before the control socket opens makes 0.45.1
|
|
351
|
-
// throw InvalidStateError and leaves the server with the old terminal size.
|
|
352
383
|
if (!window.__zjMobileUi.getState()?.data?.tabs?.length) return;
|
|
353
384
|
api = window.__zjMobileUi; term = window.term;
|
|
354
|
-
build(); applyFont();
|
|
385
|
+
build(); applyFont(); applyTheme();
|
|
386
|
+
const sync = window.__zjSyncInactiveCursorStyle;
|
|
387
|
+
window.__zjSyncInactiveCursorStyle = () => { sync?.(); applyTheme(); };
|
|
388
|
+
new MutationObserver(applyTheme).observe(root, {attributes: true, attributeFilter: ['data-zj-theme']});
|
|
355
389
|
const setData = api.setData;
|
|
356
390
|
api.setData = function(value) {setData.call(api, value); refresh();};
|
|
357
391
|
const nativeRoot = document.getElementById('zj-mobile-root');
|
|
@@ -372,14 +406,12 @@ if (typeof module === 'object') module.exports = {laneShellMode};
|
|
|
372
406
|
window.visualViewport?.addEventListener('scroll', viewport);
|
|
373
407
|
window.addEventListener('zellij:soft-keyboard-visibility', () => {
|
|
374
408
|
viewport();
|
|
375
|
-
// A rotation also shrinks the viewport. Correct upstream's height-only keyboard heuristic.
|
|
376
409
|
requestAnimationFrame(() => window.__zjSendControl?.({
|
|
377
410
|
type: 'SoftKeyboardVisibilityChanged', visible: root.classList.contains('lanes-keyboard'),
|
|
378
411
|
}));
|
|
379
412
|
});
|
|
380
413
|
document.addEventListener('focusout', () => requestAnimationFrame(viewport));
|
|
381
414
|
new MutationObserver(() => {styleLogin(); connect();}).observe(document.body, {childList: true});
|
|
382
|
-
// The upstream window hooks are installed just after it appends its mobile root.
|
|
383
415
|
const awaitingClient = setInterval(() => {connect(); if (api) clearInterval(awaitingClient);}, 200);
|
|
384
416
|
styleLogin(); connect();
|
|
385
417
|
})();
|