ai-code-engineer 0.1.0__py3-none-any.whl
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.
- ai_code_engineer/__init__.py +2 -0
- ai_code_engineer/catalog.py +143 -0
- ai_code_engineer/chat.py +181 -0
- ai_code_engineer/cli.py +384 -0
- ai_code_engineer/config.py +405 -0
- ai_code_engineer/engine.py +1282 -0
- ai_code_engineer/errors.py +27 -0
- ai_code_engineer/git_integration.py +443 -0
- ai_code_engineer/gui.py +2646 -0
- ai_code_engineer/host.py +81 -0
- ai_code_engineer/ignore.py +269 -0
- ai_code_engineer/intent.py +222 -0
- ai_code_engineer/labels.py +871 -0
- ai_code_engineer/memory.py +91 -0
- ai_code_engineer/modes.py +156 -0
- ai_code_engineer/overrides.py +540 -0
- ai_code_engineer/planbook.py +192 -0
- ai_code_engineer/providers.py +404 -0
- ai_code_engineer/redaction.py +54 -0
- ai_code_engineer/repair.py +564 -0
- ai_code_engineer/report.py +352 -0
- ai_code_engineer/runner.py +854 -0
- ai_code_engineer/setup.py +386 -0
- ai_code_engineer/symbols.py +1286 -0
- ai_code_engineer/verification.py +218 -0
- ai_code_engineer/webapp/__init__.py +1 -0
- ai_code_engineer/webapp/__main__.py +45 -0
- ai_code_engineer/webapp/contract.py +36 -0
- ai_code_engineer/webapp/controller.py +3556 -0
- ai_code_engineer/webapp/fake.py +1141 -0
- ai_code_engineer/webapp/launch.py +108 -0
- ai_code_engineer/webapp/server.py +349 -0
- ai_code_engineer/webapp/static/app.css +780 -0
- ai_code_engineer/webapp/static/app.js +2118 -0
- ai_code_engineer/webapp/static/boot.js +19 -0
- ai_code_engineer/webapp/static/index.html +89 -0
- ai_code_engineer/webapp/static/tokens.css +173 -0
- ai_code_engineer/workspace.py +385 -0
- ai_code_engineer-0.1.0.dist-info/METADATA +7 -0
- ai_code_engineer-0.1.0.dist-info/RECORD +44 -0
- ai_code_engineer-0.1.0.dist-info/WHEEL +5 -0
- ai_code_engineer-0.1.0.dist-info/entry_points.txt +2 -0
- ai_code_engineer-0.1.0.dist-info/licenses/LICENSE +21 -0
- ai_code_engineer-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
/* Component layer. Reads tokens from tokens.css; contains no literal colours. */
|
|
2
|
+
|
|
3
|
+
* { box-sizing: border-box; margin: 0; padding: 0 }
|
|
4
|
+
html, body { height: 100% }
|
|
5
|
+
body {
|
|
6
|
+
font-family: var(--ui); font-size: var(--fs); letter-spacing: var(--ls); color: var(--ink);
|
|
7
|
+
background: var(--bg); overflow: hidden; -webkit-font-smoothing: antialiased;
|
|
8
|
+
}
|
|
9
|
+
button, input, textarea, select { font: inherit; letter-spacing: inherit }
|
|
10
|
+
button { color: inherit; background: none; border: 0; cursor: pointer; white-space: nowrap }
|
|
11
|
+
a { color: var(--accent-text); text-decoration: none }
|
|
12
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px }
|
|
13
|
+
::selection { background: var(--accent-ring); color: var(--ink) }
|
|
14
|
+
|
|
15
|
+
::-webkit-scrollbar { width: 11px; height: 11px }
|
|
16
|
+
::-webkit-scrollbar-thumb {
|
|
17
|
+
background: var(--scroll); border-radius: 99px; border: 3px solid transparent; background-clip: content-box;
|
|
18
|
+
}
|
|
19
|
+
::-webkit-scrollbar-thumb:hover { background: var(--line-2); background-clip: content-box }
|
|
20
|
+
::-webkit-scrollbar-track { background: transparent }
|
|
21
|
+
|
|
22
|
+
.app { display: grid; grid-template-columns: var(--side-w) minmax(0, 1fr) var(--rail-w); height: 100vh }
|
|
23
|
+
/* The collapsed class lives on <html>, not on .app: the boot script has to set it before the
|
|
24
|
+
first paint, and the only element that exists then is the document element. */
|
|
25
|
+
html.side-collapsed .app { grid-template-columns: 62px minmax(0, 1fr) var(--rail-w) }
|
|
26
|
+
html.side-collapsed .side { padding-left: 8px; padding-right: 8px }
|
|
27
|
+
html.side-collapsed .side .brand b, html.side-collapsed .side .search,
|
|
28
|
+
html.side-collapsed .side .nav, html.side-collapsed .side .newbtn .kbd,
|
|
29
|
+
html.side-collapsed .side .foot { display: none }
|
|
30
|
+
html.side-collapsed .side .newbtn { justify-content: center; padding: 9px 0 }
|
|
31
|
+
html.side-collapsed .side .brand { gap: 4px }
|
|
32
|
+
/* The button that folds the sidebar away lives inside it, so the way back has to live outside. */
|
|
33
|
+
#sidebar-expand { display: none }
|
|
34
|
+
html.side-collapsed #sidebar-expand { display: grid }
|
|
35
|
+
|
|
36
|
+
/* ------------------------------- sidebar ------------------------------- */
|
|
37
|
+
.side {
|
|
38
|
+
background: var(--side); border-right: 1px solid var(--line);
|
|
39
|
+
display: flex; flex-direction: column; padding: 14px 12px 12px; min-height: 0;
|
|
40
|
+
}
|
|
41
|
+
.brand { display: flex; align-items: center; gap: 9px; padding: 2px 8px 14px }
|
|
42
|
+
.mark {
|
|
43
|
+
width: 24px; height: 24px; border-radius: var(--r-s); background: var(--accent); color: var(--accent-ink);
|
|
44
|
+
display: grid; place-items: center; font-weight: 700; font-size: 12px; flex: none;
|
|
45
|
+
}
|
|
46
|
+
.brand b { font-size: 14px; font-weight: 600; letter-spacing: -.15px }
|
|
47
|
+
.brand .spacer { flex: 1 }
|
|
48
|
+
.icon-btn {
|
|
49
|
+
width: 26px; height: 26px; border-radius: var(--r-s); display: grid; place-items: center; color: var(--faint);
|
|
50
|
+
transition: background var(--t-fast), color var(--t-fast);
|
|
51
|
+
}
|
|
52
|
+
.icon-btn:hover { background: var(--hover); color: var(--ink) }
|
|
53
|
+
|
|
54
|
+
.newbtn {
|
|
55
|
+
display: flex; align-items: center; gap: 8px; width: 100%; justify-content: space-between;
|
|
56
|
+
background: var(--accent); color: var(--accent-ink); padding: 9px 12px; border-radius: var(--r-m);
|
|
57
|
+
font-weight: 550; box-shadow: var(--btn-shadow); transition: background var(--t-fast), transform var(--t-fast);
|
|
58
|
+
}
|
|
59
|
+
.newbtn:hover { background: var(--accent-hover) }
|
|
60
|
+
.newbtn:active { transform: translateY(1px) }
|
|
61
|
+
.newbtn .l { display: flex; align-items: center; gap: 8px }
|
|
62
|
+
.kbd {
|
|
63
|
+
font-family: var(--mono); font-size: 10px; padding: 2px 5px; border-radius: 5px;
|
|
64
|
+
background: var(--kbd); color: var(--kbd-ink);
|
|
65
|
+
}
|
|
66
|
+
.newbtn .kbd { background: rgba(255, 255, 255, .17); color: inherit }
|
|
67
|
+
|
|
68
|
+
.search {
|
|
69
|
+
display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 8px 10px; background: var(--field);
|
|
70
|
+
border: 1px solid var(--line); border-radius: var(--r-m); color: var(--faint); font-size: 13px;
|
|
71
|
+
transition: border-color var(--t-fast), box-shadow var(--t-fast); cursor: text;
|
|
72
|
+
}
|
|
73
|
+
.search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring) }
|
|
74
|
+
.search svg { flex: none; opacity: .7 }
|
|
75
|
+
.search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 13px }
|
|
76
|
+
.search input::placeholder { color: var(--faint) }
|
|
77
|
+
|
|
78
|
+
.nav { flex: 1; min-height: 0; overflow-y: auto; margin: 0 -4px; padding: 0 4px }
|
|
79
|
+
.sec { margin-top: 18px }
|
|
80
|
+
.sec > h4 {
|
|
81
|
+
font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
|
|
82
|
+
font-weight: 600; padding: 0 8px 6px; display: flex; align-items: center; gap: 6px;
|
|
83
|
+
}
|
|
84
|
+
.sec > h4 .add { margin-left: auto; cursor: pointer; letter-spacing: 0; text-transform: none; font-size: 11px }
|
|
85
|
+
.sec > h4 .add:hover { color: var(--ink) }
|
|
86
|
+
|
|
87
|
+
.node {
|
|
88
|
+
display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: var(--r-s);
|
|
89
|
+
color: var(--ink-2); font-size: 13px; cursor: pointer; transition: background var(--t-fast);
|
|
90
|
+
}
|
|
91
|
+
.node:hover { background: var(--hover) }
|
|
92
|
+
.node .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
93
|
+
.node .chev { margin-left: auto; color: var(--faint); font-size: 9px; transition: transform var(--t-med) }
|
|
94
|
+
.node.closed .chev { transform: rotate(-90deg) }
|
|
95
|
+
.av {
|
|
96
|
+
width: 19px; height: 19px; border-radius: 6px; display: grid; place-items: center; font-size: 9.5px;
|
|
97
|
+
font-weight: 700; flex: none; /* background and ink come from avatar() in app.js */
|
|
98
|
+
}
|
|
99
|
+
.leaf {
|
|
100
|
+
display: flex; align-items: center; gap: 9px; padding: 6px 8px 6px 10px; border-radius: var(--r-s);
|
|
101
|
+
color: var(--ink-2); font-size: 13px; cursor: pointer; position: relative; transition: background var(--t-fast);
|
|
102
|
+
}
|
|
103
|
+
.leaf > span.t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
104
|
+
.leaf time { font-size: 11px; color: var(--faint); flex: none; opacity: 0; transition: opacity var(--t-fast) }
|
|
105
|
+
.leaf:hover time, .leaf.on time { opacity: 1 }
|
|
106
|
+
.leaf:hover { background: var(--hover) }
|
|
107
|
+
.leaf.on { background: var(--sel); color: var(--ink); font-weight: 550 }
|
|
108
|
+
.leaf.on::before {
|
|
109
|
+
content: ""; position: absolute; left: -4px; top: 8px; bottom: 8px; width: 2px; border-radius: 9px;
|
|
110
|
+
background: var(--accent);
|
|
111
|
+
}
|
|
112
|
+
.dot { width: 6px; height: 6px; border-radius: 99px; background: var(--line-2); flex: none }
|
|
113
|
+
|
|
114
|
+
/* Dropping a chat on a project node binds its context, so the target has to be unmistakable
|
|
115
|
+
while the drag is over it — and the same command lives on the leaf menu for keyboards. */
|
|
116
|
+
.leaf.dragging { opacity: .45 }
|
|
117
|
+
.leaf .dots {
|
|
118
|
+
flex: none; opacity: 0; border: 0; background: none; color: var(--faint); font-size: 13px;
|
|
119
|
+
line-height: 1; padding: 0 2px; cursor: pointer;
|
|
120
|
+
}
|
|
121
|
+
.leaf:hover .dots, .leaf:focus-within .dots { opacity: 1 }
|
|
122
|
+
.leaf .dots:hover { color: var(--ink) }
|
|
123
|
+
.node.drop { background: var(--sel); box-shadow: inset 0 0 0 2px var(--accent-ring) }
|
|
124
|
+
.node.drop .nm { color: var(--accent-text) }
|
|
125
|
+
/* The node's own buttons: + starts a chat in that project, ⋯ opens its menu. Both stay quiet
|
|
126
|
+
until the row is hovered or focused so the tree keeps reading as a list of names. */
|
|
127
|
+
.node .dots { opacity: 0; border: 0; background: none; color: var(--faint); font-size: 13px; line-height: 1; padding: 0 3px; flex: none }
|
|
128
|
+
.node:hover .dots, .node:focus-within .dots { opacity: 1 }
|
|
129
|
+
.node .dots:hover { color: var(--ink) }
|
|
130
|
+
.node .av { font-size: 13px }
|
|
131
|
+
.iconpick { display: flex; flex-wrap: wrap; gap: 8px }
|
|
132
|
+
.iconpick .icon {
|
|
133
|
+
width: 44px; height: 44px; font-size: 21px; display: grid; place-items: center; border-radius: var(--r-m);
|
|
134
|
+
border: 1px solid var(--line); background: var(--panel);
|
|
135
|
+
}
|
|
136
|
+
.iconpick .icon:hover { background: var(--hover); border-color: var(--line-2) }
|
|
137
|
+
.iconpick .icon.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-ring) }
|
|
138
|
+
.dot.ok { background: var(--ok) } .dot.warn { background: var(--warn) } .dot.bad { background: var(--bad) }
|
|
139
|
+
.dot.run { background: var(--accent); animation: pulse 1.3s var(--ease) infinite }
|
|
140
|
+
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .3 } }
|
|
141
|
+
.empty { padding: 14px 10px; color: var(--faint); font-size: 12.5px; line-height: 1.6 }
|
|
142
|
+
|
|
143
|
+
.side .foot {
|
|
144
|
+
margin-top: auto; padding: 12px 8px 2px; border-top: 1px solid var(--line); color: var(--faint);
|
|
145
|
+
font-size: 11.5px; display: flex; gap: 8px; align-items: center;
|
|
146
|
+
}
|
|
147
|
+
.themepick { display: flex; gap: 4px; margin-left: auto }
|
|
148
|
+
.themepick button {
|
|
149
|
+
width: 17px; height: 17px; border-radius: 5px; border: 1px solid var(--line-2);
|
|
150
|
+
display: grid; place-items: center; font-size: 9px; color: var(--ink-2);
|
|
151
|
+
}
|
|
152
|
+
.themepick button.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-ring) }
|
|
153
|
+
|
|
154
|
+
/* -------------------------------- main -------------------------------- */
|
|
155
|
+
/* min-height:0 is what keeps the conversation inside the window. A grid item's automatic minimum
|
|
156
|
+
is its content height, so a long thread grew .main past 100vh and the composer — with whatever
|
|
157
|
+
was being typed in it — fell off the bottom of a page that cannot scroll. */
|
|
158
|
+
.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg) }
|
|
159
|
+
.top {
|
|
160
|
+
display: flex; align-items: center; gap: 14px; padding: 11px 22px; min-height: 55px;
|
|
161
|
+
border-bottom: 1px solid var(--line); background: var(--topbar);
|
|
162
|
+
}
|
|
163
|
+
.top h1 { font-size: 14.5px; font-weight: 600; letter-spacing: -.2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
|
|
164
|
+
.top .sub { font-size: 11.5px; color: var(--faint); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
|
|
165
|
+
.seg { display: flex; gap: 2px; margin-left: auto; padding: 3px; background: var(--seg); border-radius: var(--r-m) }
|
|
166
|
+
.seg button {
|
|
167
|
+
padding: 5px 13px; border-radius: calc(var(--r-m) - 3px); font-size: 12.5px; color: var(--faint);
|
|
168
|
+
font-weight: 500; transition: color var(--t-fast), background var(--t-fast);
|
|
169
|
+
}
|
|
170
|
+
.seg button:hover { color: var(--ink-2) }
|
|
171
|
+
.seg button.on { background: var(--seg-on); color: var(--ink); font-weight: 550; box-shadow: var(--seg-shadow) }
|
|
172
|
+
.ghost {
|
|
173
|
+
padding: 6px 11px; border: 1px solid var(--line); border-radius: var(--r-m); font-size: 12.5px;
|
|
174
|
+
color: var(--ink-2); background: var(--panel); transition: background var(--t-fast), border-color var(--t-fast);
|
|
175
|
+
}
|
|
176
|
+
.ghost:hover { background: var(--hover); border-color: var(--line-2) }
|
|
177
|
+
|
|
178
|
+
.view { flex: 1; min-height: 0; display: none; flex-direction: column }
|
|
179
|
+
.view.on { display: flex; animation: fade var(--t-med) var(--ease) }
|
|
180
|
+
@keyframes fade { from { opacity: 0; transform: translateY(4px) } to { opacity: 1; transform: none } }
|
|
181
|
+
|
|
182
|
+
/* -------------------------------- chat -------------------------------- */
|
|
183
|
+
.scroll { flex: 1; overflow-y: auto; padding: 26px 22px 10px; scroll-behavior: smooth }
|
|
184
|
+
.thread { max-width: var(--thread-w); margin: 0 auto; display: flex; flex-direction: column; gap: 22px }
|
|
185
|
+
.msg { display: flex; gap: 11px; animation: rise var(--t-med) var(--ease) }
|
|
186
|
+
@keyframes rise { from { opacity: 0; transform: translateY(6px) } }
|
|
187
|
+
.msg .pic {
|
|
188
|
+
width: 26px; height: 26px; border-radius: var(--r-s); flex: none; display: grid; place-items: center;
|
|
189
|
+
font-size: 11px; font-weight: 700; background: var(--pic); color: var(--pic-ink);
|
|
190
|
+
}
|
|
191
|
+
.msg.me { flex-direction: row-reverse }
|
|
192
|
+
.msg.me .pic { background: var(--accent); color: var(--accent-ink) }
|
|
193
|
+
.msg.sys .pic { background: var(--chip); color: var(--ink-2) }
|
|
194
|
+
.body { min-width: 0; flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 6px }
|
|
195
|
+
.msg.me .body { align-items: flex-end }
|
|
196
|
+
.who { font-size: 11.5px; color: var(--faint); margin-bottom: 5px; font-weight: 550 }
|
|
197
|
+
/* dir="auto" on the bubble picks the paragraph direction; plaintext extends that to each
|
|
198
|
+
paragraph inside it, so an Arabic answer with an English code block reads correctly without
|
|
199
|
+
the code block being mirrored. Lists indent from the writing side, not from the left. */
|
|
200
|
+
.bub {
|
|
201
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-l); border-top-left-radius: 4px;
|
|
202
|
+
padding: 12px 15px; font-size: var(--fs); line-height: 1.62; max-width: 660px; width: fit-content;
|
|
203
|
+
box-shadow: var(--card-shadow); overflow-wrap: anywhere; unicode-bidi: plaintext;
|
|
204
|
+
}
|
|
205
|
+
.msg.me .bub {
|
|
206
|
+
background: var(--me); border-color: var(--me-line); color: var(--me-ink);
|
|
207
|
+
border-radius: var(--r-l); border-top-left-radius: var(--r-l); border-top-right-radius: 4px;
|
|
208
|
+
}
|
|
209
|
+
.bub p + p { margin-top: 9px }
|
|
210
|
+
.bub ul, .bub ol { margin: 9px 0 0; margin-inline-start: 18px; display: flex; flex-direction: column; gap: 5px }
|
|
211
|
+
.tool { unicode-bidi: plaintext }
|
|
212
|
+
/* Row actions sit under the bubble in the same faint grey as a timestamp: on every server row, all
|
|
213
|
+
the time — an icon that only exists while the pointer happens to cross its row is a control
|
|
214
|
+
nobody finds, and the row is already quiet enough to carry three of them. */
|
|
215
|
+
.macts { display: flex; gap: 2px }
|
|
216
|
+
.mact {
|
|
217
|
+
display: inline-flex; align-items: center; justify-content: center; min-width: 24px; min-height: 24px;
|
|
218
|
+
border-radius: 6px; color: var(--faint); background: transparent; border: 1px solid transparent;
|
|
219
|
+
}
|
|
220
|
+
.mact:hover, .mact:focus-visible { color: var(--ink-2); background: var(--hover); border-color: var(--line) }
|
|
221
|
+
/* the model sheet's search box */
|
|
222
|
+
.pickfilter { display: flex; align-items: center; gap: 9px }
|
|
223
|
+
.pickfilter input { flex: 1; min-width: 0 }
|
|
224
|
+
.pickfilter .g { font-size: 11px; color: var(--faint); white-space: nowrap }
|
|
225
|
+
.bub code.inl {
|
|
226
|
+
font-family: var(--mono); font-size: .92em; background: var(--chip); border: 1px solid var(--line-soft);
|
|
227
|
+
padding: 1px 5px; border-radius: 5px;
|
|
228
|
+
}
|
|
229
|
+
.bub h3 { font-size: 1.05em; margin: 12px 0 4px }
|
|
230
|
+
/* Source never mirrors: an Arabic page still shows left-to-right code. */
|
|
231
|
+
.code { margin: 11px 0 3px; border: 1px solid var(--line); border-radius: var(--r-m); overflow: hidden; background: var(--code); direction: ltr; text-align: left }
|
|
232
|
+
.code pre { direction: ltr; text-align: left }
|
|
233
|
+
.code .bar {
|
|
234
|
+
display: flex; align-items: center; gap: 8px; padding: 6px 11px; border-bottom: 1px solid var(--line);
|
|
235
|
+
font-family: var(--mono); font-size: 11px; color: var(--faint); background: var(--code-bar);
|
|
236
|
+
}
|
|
237
|
+
.code .bar b { font-weight: 500; color: var(--ink-2) }
|
|
238
|
+
.code .bar button { margin-left: auto; font-size: 11px; color: var(--faint); font-family: var(--ui) }
|
|
239
|
+
.code .bar button:hover { color: var(--ink) }
|
|
240
|
+
/* The block's own file name belongs next to the language it is written in, and it inherits the
|
|
241
|
+
bar's muted colour rather than picking a new pair. The apply button takes the Change badge's
|
|
242
|
+
graded pair — same green, same meaning: this is the road that can lead to a file. */
|
|
243
|
+
.code .bar .fname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40% }
|
|
244
|
+
.code .bar button[data-block] { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px;
|
|
245
|
+
border: 1px solid var(--ok-line); border-radius: 99px; background: var(--ok-bg);
|
|
246
|
+
color: var(--ok-ink); font-weight: 600 }
|
|
247
|
+
.code .bar button[data-block]:hover { border-color: var(--ok); color: var(--ok-ink) }
|
|
248
|
+
.code .bar button[data-block] ~ button { margin-left: 0 }
|
|
249
|
+
.code pre { padding: 11px 13px; overflow-x: auto; font-family: var(--mono); font-size: 12.5px; line-height: 1.6 }
|
|
250
|
+
.tok-k { color: var(--syn-k) } .tok-s { color: var(--syn-s) } .tok-c { color: var(--syn-c); font-style: italic }
|
|
251
|
+
.tool {
|
|
252
|
+
display: inline-flex; align-items: center; gap: 8px; padding: 5px 11px; border-radius: 99px;
|
|
253
|
+
background: var(--chip); border: 1px solid var(--line); font-size: 12px; color: var(--ink-2); cursor: pointer;
|
|
254
|
+
}
|
|
255
|
+
.tool:hover { border-color: var(--line-2) }
|
|
256
|
+
.tool b { font-weight: 600 }
|
|
257
|
+
.typing { display: inline-flex; gap: 4px; align-items: center; color: var(--faint); font-size: 12.5px; margin-top: 4px }
|
|
258
|
+
.typing i { width: 5px; height: 5px; border-radius: 99px; background: var(--accent); opacity: .3; animation: b 1.1s infinite }
|
|
259
|
+
.typing i:nth-child(2) { animation-delay: .15s } .typing i:nth-child(3) { animation-delay: .3s }
|
|
260
|
+
@keyframes b { 0%, 60%, 100% { opacity: .25; transform: translateY(0) } 30% { opacity: 1; transform: translateY(-2px) } }
|
|
261
|
+
|
|
262
|
+
/* ------------------------------ step rows (UI 4.1) ------------------------------
|
|
263
|
+
One row per thing the agent did, opening onto what it found. The sentence and its glyph arrive
|
|
264
|
+
from the server; the only thing drawn here is the chevron, and only when the server has said there
|
|
265
|
+
is something behind the row. */
|
|
266
|
+
/* `steprow`, not `step`: the plan card in the rail already owns `.step`, and two rules for one
|
|
267
|
+
name leave each surface wearing the other's layout. */
|
|
268
|
+
.steprow { display: flex; flex-direction: column; gap: 6px; max-width: 100% }
|
|
269
|
+
.st-head {
|
|
270
|
+
display: flex; align-items: flex-start; gap: 8px; padding: 5px 11px; border-radius: 99px;
|
|
271
|
+
background: var(--chip); border: 1px solid var(--line); font-size: 12px; color: var(--ink-2);
|
|
272
|
+
text-align: start; cursor: pointer; transition: border-color var(--t-fast), background var(--t-fast);
|
|
273
|
+
}
|
|
274
|
+
.st-head:hover { border-color: var(--line-2) }
|
|
275
|
+
.st-txt { min-width: 0; overflow-wrap: anywhere; unicode-bidi: plaintext }
|
|
276
|
+
.st-chev { flex: none; display: inline-flex; align-items: center; margin-inline-start: auto;
|
|
277
|
+
color: var(--faint); transition: transform var(--t-fast) }
|
|
278
|
+
.steprow.open .st-chev { transform: rotate(180deg) }
|
|
279
|
+
.steprow.live .st-head { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-ink) }
|
|
280
|
+
.st-body { display: flex; flex-direction: column; gap: 8px; padding: 2px 2px 2px 10px;
|
|
281
|
+
border-inline-start: 2px solid var(--line); }
|
|
282
|
+
.st-sec h6 { font-size: 11px; font-weight: 600; color: var(--faint); margin: 0 0 4px;
|
|
283
|
+
text-transform: uppercase; letter-spacing: .04em }
|
|
284
|
+
.st-out {
|
|
285
|
+
display: flex; flex-direction: column; gap: 1px; max-height: 34vh; overflow: auto; padding: 8px 10px;
|
|
286
|
+
border: 1px solid var(--line); border-radius: var(--r-s); background: var(--code);
|
|
287
|
+
font-family: var(--mono); font-size: 11.6px; line-height: 1.6; direction: ltr; text-align: start;
|
|
288
|
+
white-space: pre-wrap; overflow-wrap: anywhere;
|
|
289
|
+
}
|
|
290
|
+
.st-files { display: flex; flex-direction: column; gap: 1px }
|
|
291
|
+
.st-note { font-size: 12px; color: var(--faint); unicode-bidi: plaintext }
|
|
292
|
+
|
|
293
|
+
/* ------------------------------- composer ------------------------------- */
|
|
294
|
+
.dock { padding: 10px 22px 18px }
|
|
295
|
+
.composer {
|
|
296
|
+
max-width: var(--thread-w); margin: 0 auto; background: var(--panel); border: 1px solid var(--line);
|
|
297
|
+
border-radius: var(--r-xl); box-shadow: var(--pop-shadow); overflow: hidden;
|
|
298
|
+
transition: border-color var(--t-fast), box-shadow var(--t-fast);
|
|
299
|
+
}
|
|
300
|
+
.composer:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring), var(--pop-shadow) }
|
|
301
|
+
.composer textarea {
|
|
302
|
+
width: 100%; border: 0; outline: 0; resize: none; background: transparent; color: var(--ink);
|
|
303
|
+
font-size: var(--fs); line-height: 1.55; padding: 14px 16px 6px; min-height: 50px; max-height: 240px;
|
|
304
|
+
overflow-y: auto;
|
|
305
|
+
}
|
|
306
|
+
.composer textarea::placeholder { color: var(--faint) }
|
|
307
|
+
.composer.busy { border-left: 2px solid var(--accent) }
|
|
308
|
+
|
|
309
|
+
/* ------------------------------ the first-run card ------------------------------
|
|
310
|
+
The four badge pairs are the tone families the pills already ship, all of them measured in
|
|
311
|
+
tools/check_contrast.py. The word inside the badge is the point rather than the decoration: a row
|
|
312
|
+
that said only "green" is unreadable for exactly the person who cannot see green. */
|
|
313
|
+
.setup:empty { display: none }
|
|
314
|
+
.setup {
|
|
315
|
+
margin: 0 0 8px; padding: 10px 12px; background: var(--field); border: 1px solid var(--line);
|
|
316
|
+
border-radius: var(--r-l); box-shadow: var(--card-shadow); max-height: 42vh; overflow-y: auto;
|
|
317
|
+
}
|
|
318
|
+
.setup-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 6px }
|
|
319
|
+
.setup-head b { font-size: 13px }
|
|
320
|
+
.setup-tally { font-size: 12px; color: var(--faint); margin-right: auto }
|
|
321
|
+
.setup-row { display: flex; align-items: flex-start; gap: 9px; padding: 6px 0; border-top: 1px solid var(--line-soft) }
|
|
322
|
+
.setup-badge {
|
|
323
|
+
flex: none; font-size: 10.5px; font-weight: 700; letter-spacing: .04em; padding: 2px 7px;
|
|
324
|
+
border-radius: 999px; border: 1px solid transparent;
|
|
325
|
+
}
|
|
326
|
+
.setup-badge.ok { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line) }
|
|
327
|
+
.setup-badge.warn { background: var(--warn-bg); color: var(--warn-ink); border-color: var(--warn-line) }
|
|
328
|
+
.setup-badge.bad { background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-line) }
|
|
329
|
+
.setup-badge.info { background: var(--info-bg); color: var(--info-ink); border-color: var(--info-line) }
|
|
330
|
+
.setup-text { font-size: 13px; color: var(--ink-2) }
|
|
331
|
+
.setup-advice { font-size: 12px; color: var(--faint); margin-top: 2px }
|
|
332
|
+
|
|
333
|
+
/* ------------------------------ the queue strip ------------------------------
|
|
334
|
+
A queued message runs by itself when the task ends, so the strip is the record of what is
|
|
335
|
+
about to happen. Colours stay on pairs check_contrast.py already grades: ink-2 and faint on
|
|
336
|
+
panel, ink-2 on field. */
|
|
337
|
+
.queue { display: flex; flex-direction: column; gap: 4px; margin-bottom: 6px }
|
|
338
|
+
.qrow {
|
|
339
|
+
display: flex; align-items: center; gap: 9px; padding: 6px 10px; font-size: 12.5px;
|
|
340
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-m);
|
|
341
|
+
}
|
|
342
|
+
.qrow.held { background: var(--warn-bg); border-color: var(--warn-line) }
|
|
343
|
+
.qrow.held .qt, .qrow.held .qwhen { color: var(--warn-ink) }
|
|
344
|
+
.qk { flex: none; color: var(--faint); font-size: 12px }
|
|
345
|
+
.qt {
|
|
346
|
+
color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
347
|
+
}
|
|
348
|
+
.qwhen { flex: none; font-size: 11px; color: var(--faint) }
|
|
349
|
+
.qacts { margin-inline-start: auto; display: flex; gap: 2px; flex: none }
|
|
350
|
+
.qbtn {
|
|
351
|
+
padding: 3px 8px; border-radius: var(--r-s); font-size: 11.5px; color: var(--faint);
|
|
352
|
+
transition: background var(--t-fast), color var(--t-fast);
|
|
353
|
+
}
|
|
354
|
+
.qbtn:hover { background: var(--hover); color: var(--accent-text) }
|
|
355
|
+
.qedit {
|
|
356
|
+
flex: 1; min-width: 0; background: var(--field); border: 1px solid var(--line);
|
|
357
|
+
border-radius: var(--r-s); padding: 4px 8px; font-family: var(--ui); font-size: 12.5px;
|
|
358
|
+
color: var(--ink-2);
|
|
359
|
+
}
|
|
360
|
+
.qnote { font-size: 11.5px; color: var(--faint); padding: 0 10px }
|
|
361
|
+
.cbar { display: flex; align-items: center; gap: 7px; padding: 8px 10px 9px 12px; border-top: 1px solid var(--line-soft); flex-wrap: wrap }
|
|
362
|
+
.pill {
|
|
363
|
+
display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 5px 10px; border-radius: 99px;
|
|
364
|
+
border: 1px solid var(--line); background: var(--panel); font-size: 12px; color: var(--ink-2);
|
|
365
|
+
transition: background var(--t-fast), border-color var(--t-fast);
|
|
366
|
+
}
|
|
367
|
+
button.pill:hover { background: var(--hover); border-color: var(--line-2) }
|
|
368
|
+
.pill svg { opacity: .6; flex: none }
|
|
369
|
+
.pill.mono { font-family: var(--mono); font-size: 11.5px }
|
|
370
|
+
.pill.soft { border-color: transparent; background: var(--chip) }
|
|
371
|
+
.pill.on { border-color: var(--accent); color: var(--accent-text) }
|
|
372
|
+
.pill .x { opacity: .5; font-size: 13px; line-height: 1 }
|
|
373
|
+
/* The three composer modes reuse pairs the contrast audit already grades: chip ink for prose, the
|
|
374
|
+
info blue for a folder it will only read, the OK green for "this can change files". */
|
|
375
|
+
.pill.mode { font-weight: 600 }
|
|
376
|
+
.pill.mode.chat { border-color: transparent; background: var(--chip); color: var(--ink-2) }
|
|
377
|
+
.pill.mode.read { border-color: var(--info-line); background: var(--info-bg); color: var(--info-ink) }
|
|
378
|
+
.pill.mode.change { border-color: var(--ok-line); background: var(--ok-bg); color: var(--ok-ink) }
|
|
379
|
+
/* A folder sealed by another surface keeps its own badge colour, but the lock is drawn and the border
|
|
380
|
+
goes dashed: the difference between "I chose this" and "this was chosen for the folder" has to be
|
|
381
|
+
visible without reading the tooltip. */
|
|
382
|
+
.pill.mode.sealed { border-style: dashed }
|
|
383
|
+
.pill.path { max-width: 300px; overflow: hidden; text-overflow: ellipsis; color: var(--faint) }
|
|
384
|
+
/* The git chip reuses the two pairs the contrast audit already grades: a clean tree is
|
|
385
|
+
the same green as a passing run, uncommitted work the same amber as an unverified one. */
|
|
386
|
+
.pill.git.clean { border-color: var(--ok-line); background: var(--ok-bg); color: var(--ok-ink) }
|
|
387
|
+
.pill.git.dirty { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-ink) }
|
|
388
|
+
.pill.git b { font-weight: 700; margin-inline-start: 4px }
|
|
389
|
+
/* The restore pill is the one control here that replaces bytes, so it wears the same red as a failed
|
|
390
|
+
run. That pair is already graded by the contrast audit, so nothing new is being invented. */
|
|
391
|
+
.pill.restore { border-color: var(--bad-line); background: var(--bad-bg); color: var(--bad-ink);
|
|
392
|
+
font-weight: 600 }
|
|
393
|
+
/* ON is the amber pair the rest of the window already uses for "look at this state", because
|
|
394
|
+
a folder that writes without asking is exactly that. OFF is the same neutral chip as Chat. */
|
|
395
|
+
.pill.auto.off { border-color: transparent; background: var(--chip); color: var(--ink-2) }
|
|
396
|
+
.pill.auto.on { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-ink);
|
|
397
|
+
font-weight: 600 }
|
|
398
|
+
/* The amber is the same claim the Auto-Apply switch makes, and it stays on the card until the task
|
|
399
|
+
changes: a write nobody clicked for should not be easy to miss. It is a sentence on its own now —
|
|
400
|
+
the undo that used to sit inside it is the action bar under the file list. */
|
|
401
|
+
.auto-note { margin: 0 0 9px; padding: 7px 10px; border: 1px solid var(--warn-line);
|
|
402
|
+
border-radius: var(--r-m); background: var(--warn-bg); color: var(--warn-ink); font-size: 12.5px }
|
|
403
|
+
/* What the Run buttons actually do, written where they are about to be pressed. It used to live in
|
|
404
|
+
the runner's module docstring, which nobody reads before clicking. */
|
|
405
|
+
.warn { margin-top: 9px; padding: 6px 9px; border: 1px solid var(--warn-line);
|
|
406
|
+
border-radius: var(--r-m); background: var(--warn-bg); color: var(--warn-ink);
|
|
407
|
+
font-size: 12px; line-height: 1.45 }
|
|
408
|
+
.pill.path svg { opacity: .5 }
|
|
409
|
+
/* The folder picker's location bar, the drive chips, and the path you can type instead of
|
|
410
|
+
clicking through to a disk the picker did not start on. */
|
|
411
|
+
.fshead { display: flex; flex-direction: column; gap: 7px; margin-bottom: 9px }
|
|
412
|
+
.fshead .where {
|
|
413
|
+
font-family: var(--mono); font-size: 11.5px; color: var(--faint); overflow: hidden;
|
|
414
|
+
text-overflow: ellipsis; white-space: nowrap;
|
|
415
|
+
}
|
|
416
|
+
.fshead .jump {
|
|
417
|
+
width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: var(--r-s);
|
|
418
|
+
background: var(--panel); color: var(--ink); font-family: var(--mono); font-size: 12px;
|
|
419
|
+
}
|
|
420
|
+
.fshead .jump:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring) }
|
|
421
|
+
.drives { display: flex; flex-wrap: wrap; gap: 6px }
|
|
422
|
+
.drives:empty { display: none }
|
|
423
|
+
button.drive {
|
|
424
|
+
padding: 4px 10px; border: 1px solid var(--line); border-radius: 99px; background: var(--chip);
|
|
425
|
+
color: var(--ink-2); font-family: var(--mono); font-size: 11.5px;
|
|
426
|
+
}
|
|
427
|
+
button.drive:hover { border-color: var(--line-2); background: var(--hover) }
|
|
428
|
+
.send {
|
|
429
|
+
margin-left: auto; display: inline-flex; align-items: center; gap: 7px; background: var(--accent);
|
|
430
|
+
color: var(--accent-ink); padding: 7px 15px; border-radius: 99px; font-size: 13px; font-weight: 600;
|
|
431
|
+
box-shadow: var(--btn-shadow); transition: background var(--t-fast), transform var(--t-fast);
|
|
432
|
+
}
|
|
433
|
+
.send:hover:not(:disabled) { background: var(--accent-hover) }
|
|
434
|
+
.send:active:not(:disabled) { transform: translateY(1px) }
|
|
435
|
+
.send.stop { background: var(--panel); color: var(--ink); border: 1px solid var(--line); box-shadow: none; font-weight: 500; margin-left: 0 }
|
|
436
|
+
.send:disabled { opacity: .45; cursor: not-allowed }
|
|
437
|
+
|
|
438
|
+
/* ------------------------------ right rail ------------------------------ */
|
|
439
|
+
.rail {
|
|
440
|
+
background: var(--side); border-left: 1px solid var(--line); padding: 14px 14px 18px; overflow-y: auto;
|
|
441
|
+
display: flex; flex-direction: column; gap: 12px; min-height: 0;
|
|
442
|
+
}
|
|
443
|
+
.card {
|
|
444
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-l); padding: 14px;
|
|
445
|
+
box-shadow: var(--card-shadow);
|
|
446
|
+
}
|
|
447
|
+
.card h5 {
|
|
448
|
+
font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--faint);
|
|
449
|
+
font-weight: 600; margin-bottom: 9px; display: flex; align-items: center; gap: 6px;
|
|
450
|
+
}
|
|
451
|
+
.card .t { font-size: 13.5px; font-weight: 600; letter-spacing: -.1px; line-height: 1.4 }
|
|
452
|
+
.card .d { font-size: 12px; color: var(--faint); line-height: 1.55; margin-top: 4px }
|
|
453
|
+
.hr { height: 1px; background: var(--line); margin: 12px -14px; padding: 0 }
|
|
454
|
+
.link {
|
|
455
|
+
display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2); font-weight: 500;
|
|
456
|
+
padding: 6px 0; border-radius: 6px; cursor: pointer; width: 100%; text-align: left;
|
|
457
|
+
}
|
|
458
|
+
.link:hover { color: var(--ink) }
|
|
459
|
+
.link.quiet { cursor: default; color: var(--faint) }
|
|
460
|
+
.link.quiet:hover { color: var(--faint) }
|
|
461
|
+
.link svg { flex: none; opacity: .6 }
|
|
462
|
+
.link .r { margin-left: auto; color: var(--faint); font-size: 11.5px; font-weight: 400 }
|
|
463
|
+
.state {
|
|
464
|
+
display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; padding: 3px 9px;
|
|
465
|
+
border-radius: 99px; background: var(--warn-bg); color: var(--warn-ink); border: 1px solid var(--warn-line);
|
|
466
|
+
margin-bottom: 9px;
|
|
467
|
+
}
|
|
468
|
+
.state.ok { background: var(--ok-bg); color: var(--ok-ink); border-color: var(--ok-line) }
|
|
469
|
+
.state.bad { background: var(--bad-bg); color: var(--bad-ink); border-color: var(--bad-line) }
|
|
470
|
+
.state.idle { background: var(--chip); color: var(--faint); border-color: var(--line) }
|
|
471
|
+
.row { display: flex; gap: 8px; align-items: center }
|
|
472
|
+
/* One override row in the drawer: the value the run uses, then who said so. */
|
|
473
|
+
.ovrow { display: flex; gap: 8px; align-items: baseline; padding: 3px 0 }
|
|
474
|
+
.ovrow .ovk { font-variant-numeric: tabular-nums; min-width: 210px }
|
|
475
|
+
.ovrow .ovm { color: var(--faint); font-size: 11.5px; flex: 1 }
|
|
476
|
+
.bar { height: 4px; border-radius: 99px; background: var(--line); overflow: hidden; margin-top: 9px }
|
|
477
|
+
.bar i { display: block; height: 100%; background: var(--accent); border-radius: 99px; transition: width var(--t-med) }
|
|
478
|
+
.meta { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--faint); margin-top: 7px; gap: 8px }
|
|
479
|
+
.step { display: flex; gap: 9px; align-items: flex-start; font-size: 12.5px; color: var(--ink-2); padding: 4px 0 }
|
|
480
|
+
.step i {
|
|
481
|
+
width: 15px; height: 15px; border-radius: 99px; border: 1.5px solid var(--line-2); flex: none; margin-top: 1px;
|
|
482
|
+
display: grid; place-items: center; font-size: 8.5px; font-style: normal; color: var(--faint);
|
|
483
|
+
}
|
|
484
|
+
.step.done i { background: var(--ok); border-color: var(--ok); color: var(--on-ok) }
|
|
485
|
+
.step.now i { border-color: var(--accent); color: var(--accent-text); box-shadow: 0 0 0 3px var(--accent-ring) }
|
|
486
|
+
.step.now { color: var(--ink); font-weight: 550 }
|
|
487
|
+
.step span { min-width: 0; overflow-wrap: anywhere }
|
|
488
|
+
|
|
489
|
+
/* ------------------------------ diffs and decisions ------------------------------
|
|
490
|
+
The Changes pane was deleted in UI 4.0 and its surfaces moved into the rail and the sheet, so
|
|
491
|
+
this block keeps only what those two still mount: the per-file badge, the tab strip, the diff
|
|
492
|
+
body and the buttons a change set asks for. The pane's own layout wrappers went with it. */
|
|
493
|
+
.badge {
|
|
494
|
+
font-size: 10px; font-weight: 700; width: 17px; height: 17px; border-radius: 5px; display: grid;
|
|
495
|
+
place-items: center; flex: none; background: var(--chip); color: var(--ink-2);
|
|
496
|
+
}
|
|
497
|
+
.badge.M { background: var(--warn-bg); color: var(--warn-ink) }
|
|
498
|
+
.badge.A { background: var(--ok-bg); color: var(--ok-ink) }
|
|
499
|
+
.badge.D { background: var(--bad-bg); color: var(--bad-ink) }
|
|
500
|
+
.dtabs { display: flex; gap: 2px; padding: 7px 9px; border-bottom: 1px solid var(--line); align-items: center; flex: none }
|
|
501
|
+
.dtabs button { padding: 4px 11px; border-radius: 8px; font-size: 12.5px; color: var(--faint); transition: color var(--t-fast) }
|
|
502
|
+
.dtabs button:hover { color: var(--ink-2) }
|
|
503
|
+
.dtabs button.on { background: var(--seg-on); color: var(--ink); font-weight: 550; box-shadow: var(--seg-shadow) }
|
|
504
|
+
.dcode { flex: 1; overflow: auto; font-family: var(--mono); font-size: 12.2px; line-height: 1.65 }
|
|
505
|
+
.dline { display: flex; white-space: pre }
|
|
506
|
+
.dline .ln {
|
|
507
|
+
width: 40px; flex: none; text-align: right; padding: 0 10px; color: var(--faint); background: var(--code-bar);
|
|
508
|
+
border-right: 1px solid var(--line); user-select: none; font-size: 11px;
|
|
509
|
+
}
|
|
510
|
+
.dline .tx { padding: 0 12px; flex: 1; min-width: 0 }
|
|
511
|
+
.dline.add .tx { background: var(--add); color: var(--add-ink) }
|
|
512
|
+
.dline.del .tx { background: var(--del); color: var(--del-ink) }
|
|
513
|
+
.dline.hunk .tx { color: var(--faint); background: var(--hunk) }
|
|
514
|
+
.solid {
|
|
515
|
+
background: var(--accent); color: var(--accent-ink); padding: 8px 16px; border-radius: var(--r-m);
|
|
516
|
+
font-weight: 600; font-size: 13px; box-shadow: var(--btn-shadow); transition: background var(--t-fast);
|
|
517
|
+
}
|
|
518
|
+
.solid:hover:not(:disabled) { background: var(--accent-hover) }
|
|
519
|
+
.solid:disabled { opacity: .45; cursor: not-allowed; box-shadow: none }
|
|
520
|
+
.line-btn {
|
|
521
|
+
padding: 8px 14px; border-radius: var(--r-m); border: 1px solid var(--line); background: var(--panel);
|
|
522
|
+
font-size: 13px; color: var(--ink-2); transition: background var(--t-fast), border-color var(--t-fast);
|
|
523
|
+
}
|
|
524
|
+
.line-btn:hover:not(:disabled) { background: var(--hover); border-color: var(--line-2) }
|
|
525
|
+
.line-btn:disabled { opacity: .45; cursor: not-allowed }
|
|
526
|
+
|
|
527
|
+
/* ------------------------------ activity view ---------------------------- */
|
|
528
|
+
.logwrap { flex: 1; min-height: 0; overflow: auto; padding: 16px 22px }
|
|
529
|
+
.log {
|
|
530
|
+
max-width: 900px; margin: 0 auto; font-family: var(--mono); font-size: 12px; line-height: 1.85;
|
|
531
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-l); padding: 14px 16px;
|
|
532
|
+
box-shadow: var(--card-shadow);
|
|
533
|
+
}
|
|
534
|
+
.log .r { display: flex; gap: 12px }
|
|
535
|
+
.log .ts { color: var(--faint); flex: none }
|
|
536
|
+
.log .k { color: var(--accent-text); flex: none; font-family: var(--ui); font-size: 11px; padding-top: 1px }
|
|
537
|
+
.log .m { color: var(--ink-2); min-width: 0; overflow-wrap: anywhere }
|
|
538
|
+
/* A streamed build line is transcript, not a status note: tighter, quieter, and LTR like
|
|
539
|
+
the compiler wrote it, even when the interface is Arabic. */
|
|
540
|
+
.log .r.chunk { line-height: 1.45 }
|
|
541
|
+
.log .r.chunk .m { color: var(--faint); direction: ltr; text-align: start }
|
|
542
|
+
|
|
543
|
+
/* -------------------------------- toasts -------------------------------- */
|
|
544
|
+
.toasts { position: fixed; right: calc(var(--rail-w) + 22px); bottom: 22px; display: flex; flex-direction: column; gap: 8px; z-index: 60 }
|
|
545
|
+
.toast {
|
|
546
|
+
display: flex; align-items: center; gap: 10px; background: var(--pop); color: var(--pop-ink); padding: 10px 14px;
|
|
547
|
+
border-radius: var(--r-m); font-size: 12.5px; box-shadow: var(--pop-shadow); max-width: 460px;
|
|
548
|
+
animation: toast-in var(--t-med) var(--ease);
|
|
549
|
+
}
|
|
550
|
+
@keyframes toast-in { from { opacity: 0; transform: translateY(10px) scale(.98) } }
|
|
551
|
+
.toast.out { animation: toast-out 180ms var(--ease) forwards }
|
|
552
|
+
@keyframes toast-out { to { opacity: 0; transform: translateY(6px) } }
|
|
553
|
+
.toast i { width: 7px; height: 7px; border-radius: 99px; background: var(--ok); flex: none }
|
|
554
|
+
.toast.warn i { background: var(--warn) } .toast.bad i { background: var(--bad) }
|
|
555
|
+
.toast button { margin-left: auto; opacity: .6; font-size: 14px; line-height: 1 }
|
|
556
|
+
.toast button:hover { opacity: 1 }
|
|
557
|
+
/* The disconnect notice carries a real action beside its close glyph, so it cannot borrow the
|
|
558
|
+
14px mark every other toast uses — "Reload" has to read as a word, not a footnote. */
|
|
559
|
+
.toast [data-reload] {
|
|
560
|
+
font-size: 12.5px; line-height: 1.3; padding: 4px 9px; opacity: 1;
|
|
561
|
+
border: 1px solid var(--line); border-radius: var(--r-s); background: var(--panel); color: var(--ink);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* -------------------------------- modals -------------------------------- */
|
|
565
|
+
.scrim {
|
|
566
|
+
position: fixed; inset: 0; background: var(--scrim); display: grid; place-items: center; z-index: 80;
|
|
567
|
+
animation: fade-in 140ms var(--ease); padding: 24px;
|
|
568
|
+
}
|
|
569
|
+
@keyframes fade-in { from { opacity: 0 } }
|
|
570
|
+
.scrim.out { animation: fade-in 140ms var(--ease) reverse forwards }
|
|
571
|
+
.sheet {
|
|
572
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-xl); box-shadow: var(--pop-shadow);
|
|
573
|
+
width: min(560px, 100%); max-height: 84vh; display: flex; flex-direction: column; overflow: hidden;
|
|
574
|
+
animation: sheet-in 200ms var(--ease);
|
|
575
|
+
}
|
|
576
|
+
@keyframes sheet-in { from { opacity: 0; transform: translateY(10px) scale(.985) } }
|
|
577
|
+
.sheet.wide { width: min(820px, 100%) }
|
|
578
|
+
.sheet header { padding: 18px 20px 4px }
|
|
579
|
+
.sheet header h3 { font-size: 15.5px; font-weight: 600; letter-spacing: -.2px }
|
|
580
|
+
.sheet header p { font-size: 13px; color: var(--faint); margin-top: 6px; line-height: 1.6; overflow-wrap: anywhere }
|
|
581
|
+
.sheet .content { padding: 14px 20px; overflow-y: auto; min-height: 0 }
|
|
582
|
+
.sheet footer { display: flex; gap: 8px; padding: 14px 20px 18px; justify-content: flex-end; border-top: 1px solid var(--line-soft) }
|
|
583
|
+
.sheet .warnbox {
|
|
584
|
+
background: var(--warn-bg); border: 1px solid var(--warn-line); color: var(--warn-ink); padding: 10px 12px;
|
|
585
|
+
border-radius: var(--r-m); font-size: 12.5px; line-height: 1.55; margin-bottom: 12px;
|
|
586
|
+
}
|
|
587
|
+
/* -------------------------------- module graph -------------------------------- */
|
|
588
|
+
/* The SVG is drawn by app.js from server data, so this layer only paints what the drawing names: no
|
|
589
|
+
literal colours here either, and the arrows inherit the line token so a dark and a light theme read
|
|
590
|
+
the same way. */
|
|
591
|
+
.gscroll { overflow: auto; max-width: 100%; margin: 12px 0; border: 1px solid var(--line-soft);
|
|
592
|
+
border-radius: var(--r-m); background: var(--hover) }
|
|
593
|
+
.graph { display: block }
|
|
594
|
+
.graph .gnode rect { fill: var(--bg); stroke: var(--line-2); stroke-width: 1 }
|
|
595
|
+
.graph .gnode text { font-family: var(--mono); font-size: 11.5px; fill: var(--ink) }
|
|
596
|
+
.graph .gedge { fill: none; stroke: var(--line-2) }
|
|
597
|
+
.graph .ghead { fill: var(--faint); stroke: none }
|
|
598
|
+
.graph .gedge:hover, .graph .gnode:hover rect { stroke: var(--accent) }
|
|
599
|
+
.gmods { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px }
|
|
600
|
+
.gmod b { font-weight: 600 }
|
|
601
|
+
.gmod .mono { font-size: 11.5px }
|
|
602
|
+
/* -------------------------------- project drawer -------------------------------- */
|
|
603
|
+
.mono { font-family: var(--mono) }
|
|
604
|
+
.quiet { color: var(--faint) }
|
|
605
|
+
.small { font-size: 11.5px; line-height: 1.6 }
|
|
606
|
+
.dsec { border-top: 1px solid var(--line-soft); padding: 14px 0 4px; margin-bottom: 6px }
|
|
607
|
+
.dsec:first-child { border-top: 0; padding-top: 2px }
|
|
608
|
+
.dsec h6 { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--faint); margin-bottom: 9px }
|
|
609
|
+
.dsec .pathline { font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; line-height: 1.5; margin-bottom: 10px }
|
|
610
|
+
.dsec textarea { width: 100%; font-family: var(--ui); font-size: 13px; line-height: 1.6 }
|
|
611
|
+
.meter { display: grid; grid-template-columns: 1fr 2fr auto; gap: 10px; align-items: center; margin-bottom: 7px }
|
|
612
|
+
.meter .t { font-size: 12.5px; color: var(--ink-2) }
|
|
613
|
+
.meter .v { font-size: 11.5px; color: var(--faint); white-space: nowrap }
|
|
614
|
+
.meter .bar { height: 6px; background: var(--chip); border: 1px solid var(--line-soft); border-radius: 99px; overflow: hidden }
|
|
615
|
+
.meter .bar i { display: block; height: 100%; background: var(--accent) }
|
|
616
|
+
.recipe .g { font-family: var(--mono); overflow-wrap: anywhere; text-align: end; max-width: 46% }
|
|
617
|
+
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px }
|
|
618
|
+
.field label { font-size: 12px; color: var(--faint); font-weight: 550 }
|
|
619
|
+
.field input, .field textarea, .field select {
|
|
620
|
+
background: var(--field); border: 1px solid var(--line); border-radius: var(--r-m); padding: 9px 11px;
|
|
621
|
+
color: var(--ink); outline: 0; font-size: 13px; width: 100%;
|
|
622
|
+
}
|
|
623
|
+
.field input:focus, .field textarea:focus, .field select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring) }
|
|
624
|
+
.field textarea { font-family: var(--ui); line-height: 1.6; resize: vertical; min-height: 120px }
|
|
625
|
+
.field .hint { font-size: 11.5px; color: var(--faint); line-height: 1.5 }
|
|
626
|
+
.switch { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--ink-2); cursor: pointer; padding: 4px 0 }
|
|
627
|
+
.switch input { width: auto }
|
|
628
|
+
|
|
629
|
+
/* folder browser */
|
|
630
|
+
.tree { max-height: 46vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px }
|
|
631
|
+
.trow {
|
|
632
|
+
display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: var(--r-s); font-size: 13px;
|
|
633
|
+
cursor: pointer; color: var(--ink-2);
|
|
634
|
+
}
|
|
635
|
+
.trow:hover { background: var(--hover) }
|
|
636
|
+
.trow .chev { width: 10px; font-size: 9px; color: var(--faint); flex: none }
|
|
637
|
+
.trow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
638
|
+
.trow.leaf-file { font-family: var(--mono); font-size: 12px }
|
|
639
|
+
|
|
640
|
+
/* command palette */
|
|
641
|
+
.palette { width: min(600px, 100%); padding: 0 }
|
|
642
|
+
.palette .cmdinput {
|
|
643
|
+
width: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 15px;
|
|
644
|
+
padding: 17px 20px; border-bottom: 1px solid var(--line);
|
|
645
|
+
}
|
|
646
|
+
.cmdlist { max-height: 46vh; overflow-y: auto; padding: 6px }
|
|
647
|
+
.cmd {
|
|
648
|
+
display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--r-m); font-size: 13px;
|
|
649
|
+
color: var(--ink-2); cursor: pointer; width: 100%; text-align: left;
|
|
650
|
+
}
|
|
651
|
+
.cmd .g { margin-left: auto; font-size: 11px; color: var(--faint) }
|
|
652
|
+
.cmd.on { background: var(--sel); color: var(--ink) }
|
|
653
|
+
/* The mode sheet spells each promise out under its row instead of hiding it in a title attribute:
|
|
654
|
+
which of the three positions refuses to write is the whole decision, and it has to be readable
|
|
655
|
+
before the click. `faint` on the sheet's own surface is the pair the card labels already use. */
|
|
656
|
+
.cmd-help { padding: 0 12px 10px; font-size: 12px; line-height: 1.45; color: var(--faint) }
|
|
657
|
+
|
|
658
|
+
/* ------------------------- file chips and the rail preview (UI 3.1) -------------------------
|
|
659
|
+
A chip is the smallest control that says "this file changed, and you can look". The three pairs
|
|
660
|
+
these surfaces introduced — ink-2 on field, faint on field, ink-2 on panel — were added to
|
|
661
|
+
tools/check_contrast.py rather than assumed to inherit the pills' numbers. */
|
|
662
|
+
.chat-task-card {
|
|
663
|
+
margin-top: 8px; padding: 9px 11px; background: var(--panel); border: 1px solid var(--line);
|
|
664
|
+
border-radius: var(--r-m); display: flex; flex-direction: column; gap: 8px; max-width: 100%;
|
|
665
|
+
}
|
|
666
|
+
.chat-task-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap }
|
|
667
|
+
.chat-task-head .quiet { font-size: 12px; color: var(--faint); overflow-wrap: anywhere }
|
|
668
|
+
.chat-file-chips { display: flex; flex-wrap: wrap; gap: 6px }
|
|
669
|
+
.file-chip {
|
|
670
|
+
display: inline-flex; align-items: center; gap: 7px; padding: 4px 9px; font-size: 12.5px;
|
|
671
|
+
background: var(--field); border: 1px solid var(--line); border-radius: var(--r-s);
|
|
672
|
+
color: var(--ink-2); transition: background var(--t-fast), border-color var(--t-fast);
|
|
673
|
+
}
|
|
674
|
+
.file-chip:hover { background: var(--hover); border-color: var(--accent) }
|
|
675
|
+
.file-chip .chip-name {
|
|
676
|
+
font-family: var(--mono); font-size: 12px; direction: ltr; unicode-bidi: isolate;
|
|
677
|
+
max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
678
|
+
}
|
|
679
|
+
.chip-tag { font-size: 10.5px; font-weight: 700; padding: 2px 6px; border-radius: 5px; flex: none }
|
|
680
|
+
.chip-tag.created { background: var(--ok-bg); color: var(--ok-ink) }
|
|
681
|
+
.chip-tag.modified { background: var(--warn-bg); color: var(--warn-ink) }
|
|
682
|
+
/* The removal badge. `.badge.D` has had its colour since the diff view; a chip is newer, and a file
|
|
683
|
+
that is going away wearing an amber "Modified" tag is the one misread worth spending a rule on. */
|
|
684
|
+
.chip-tag.deleted { background: var(--bad-bg); color: var(--bad-ink) }
|
|
685
|
+
.file-chip .chip-action {
|
|
686
|
+
margin-inline-start: auto; font-size: 11.5px; color: var(--faint); white-space: nowrap;
|
|
687
|
+
}
|
|
688
|
+
.file-chip:hover .chip-action { color: var(--accent-text) }
|
|
689
|
+
|
|
690
|
+
/* The rail's own file list: the same control as a chip, in the width the card has. */
|
|
691
|
+
.rail-files { display: flex; flex-direction: column; margin-top: 9px; gap: 1px }
|
|
692
|
+
.rfile {
|
|
693
|
+
display: flex; align-items: center; gap: 8px; padding: 6px 7px; border-radius: var(--r-s);
|
|
694
|
+
font-size: 12.5px; color: var(--ink-2); text-align: start; transition: background var(--t-fast);
|
|
695
|
+
}
|
|
696
|
+
.rfile:hover { background: var(--hover) }
|
|
697
|
+
.rfile code {
|
|
698
|
+
font-family: var(--mono); font-size: 11.8px; direction: ltr; unicode-bidi: isolate;
|
|
699
|
+
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
700
|
+
}
|
|
701
|
+
.rail-preview { display: flex; flex-direction: column; gap: 8px }
|
|
702
|
+
.pv-head { display: flex; align-items: center; gap: 8px }
|
|
703
|
+
.pv-head code {
|
|
704
|
+
font-family: var(--mono); font-size: 12px; direction: ltr; unicode-bidi: isolate; min-width: 0;
|
|
705
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
706
|
+
}
|
|
707
|
+
.pv-head .num { margin-inline-start: auto; font-family: var(--mono); font-size: 11.5px; display: flex; gap: 7px; flex: none }
|
|
708
|
+
.pv-head .num .p { color: var(--ok) } .pv-head .num .n { color: var(--bad) }
|
|
709
|
+
.pv-note { font-size: 11.5px; color: var(--faint) }
|
|
710
|
+
.pv-tabs { padding: 5px 0; border-bottom: 1px solid var(--line) }
|
|
711
|
+
.pv-tabs button { padding: 3px 9px; font-size: 12px }
|
|
712
|
+
/* The column is 322px and a diff has no natural height, so it scrolls inside the card rather than
|
|
713
|
+
pushing the Checks and Sources cards out of the window. */
|
|
714
|
+
.pv-body { max-height: 46vh; border: 1px solid var(--line); border-radius: var(--r-s); background: var(--code) }
|
|
715
|
+
.pv-empty { padding: 10px 12px; font-family: var(--ui); font-size: 12px; color: var(--faint) }
|
|
716
|
+
/* Server-built sentences on the Checks tab: the checks the model promised, so prose rather than a
|
|
717
|
+
diff — mono would read as code that ran. */
|
|
718
|
+
.pv-checks {
|
|
719
|
+
padding: 10px 12px; font-family: var(--ui); font-size: 12px; line-height: 1.75;
|
|
720
|
+
white-space: pre-wrap; overflow-wrap: anywhere;
|
|
721
|
+
}
|
|
722
|
+
/* The three decisions about a change set. The pane that held them at pane width is gone, so the
|
|
723
|
+
rail stacks them full width and the sheet puts them back on one line. */
|
|
724
|
+
.pv-acts { display: flex; flex-direction: column; gap: 7px }
|
|
725
|
+
.pv-acts button { width: 100% }
|
|
726
|
+
.pv-sheet .pv-acts { flex-direction: row; flex-wrap: wrap }
|
|
727
|
+
.pv-sheet .pv-acts button { width: auto; flex: 1 1 auto; min-width: 132px }
|
|
728
|
+
|
|
729
|
+
/* The change viewer's second surface: below 1180px the rail is display:none, so the same card is
|
|
730
|
+
mounted in a wide sheet over the chat and gets the height the rail cannot spare. */
|
|
731
|
+
.pv-sheet { display: flex; flex-direction: column; gap: 10px }
|
|
732
|
+
.pv-sheet .pv-body { max-height: 58vh }
|
|
733
|
+
.pv-sheet .pv-body { max-height: 58vh }
|
|
734
|
+
.pv-sheet .pv-acts + .pv-acts { display: none }
|
|
735
|
+
|
|
736
|
+
/* settings sheet grid */
|
|
737
|
+
.tabs { display: flex; gap: 2px; padding: 0 20px; border-bottom: 1px solid var(--line) }
|
|
738
|
+
.tabs button { padding: 10px 12px; font-size: 13px; color: var(--faint); border-bottom: 2px solid transparent; margin-bottom: -1px }
|
|
739
|
+
.tabs button.on { color: var(--ink); border-bottom-color: var(--accent); font-weight: 550 }
|
|
740
|
+
|
|
741
|
+
/* skeleton */
|
|
742
|
+
@keyframes sk { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }
|
|
743
|
+
|
|
744
|
+
/* progress line under the header while busy */
|
|
745
|
+
/* The activity strip: one line under the header saying what the program is doing right now, and
|
|
746
|
+
after a job ends the last sentence it said. The shimmer is the "something is running" signal at
|
|
747
|
+
2px; the words are the answer to "what", which used to be written into the header subtitle. */
|
|
748
|
+
.busybar {
|
|
749
|
+
position: relative; display: flex; align-items: center; min-height: 26px; padding: 0 16px;
|
|
750
|
+
font-size: 11.5px; color: var(--faint); flex: none; overflow: hidden;
|
|
751
|
+
text-overflow: ellipsis; white-space: nowrap; border-top: 1px solid var(--line-soft);
|
|
752
|
+
}
|
|
753
|
+
.busybar::after {
|
|
754
|
+
content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px; opacity: 0;
|
|
755
|
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
756
|
+
background-size: 200% 100%; animation: sk 1.1s linear infinite;
|
|
757
|
+
}
|
|
758
|
+
.busybar.busy::after { opacity: 1 }
|
|
759
|
+
.hidden { display: none !important }
|
|
760
|
+
/* Transitions are for user-triggered changes only; on launch they animate every token. */
|
|
761
|
+
.booting *, .booting *::before, .booting *::after { transition: none !important; animation: none !important }
|
|
762
|
+
|
|
763
|
+
@media (prefers-reduced-motion: reduce) {
|
|
764
|
+
*, *::before, *::after {
|
|
765
|
+
animation-duration: .01ms !important; animation-iteration-count: 1 !important;
|
|
766
|
+
transition-duration: .01ms !important; scroll-behavior: auto !important;
|
|
767
|
+
}
|
|
768
|
+
.typing i { animation: none !important; opacity: .8 }
|
|
769
|
+
.busybar::after { animation: none !important }
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@media (max-width: 1180px) {
|
|
773
|
+
.app { grid-template-columns: var(--side-w) minmax(0, 1fr) }
|
|
774
|
+
.rail { display: none }
|
|
775
|
+
.toasts { right: 22px }
|
|
776
|
+
}
|
|
777
|
+
@media (max-width: 860px) {
|
|
778
|
+
.app { grid-template-columns: minmax(0, 1fr) }
|
|
779
|
+
.side { display: none }
|
|
780
|
+
}
|