@firstpick/pi-package-webui 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/pi-webui.mjs +12 -3
- package/package.json +3 -2
- package/public/app.js +703 -32
- package/public/index.html +2 -2
- package/public/styles.css +307 -1
- package/tests/http-endpoints-harness.test.mjs +15 -1
- package/tests/mobile-static.test.mjs +28 -3
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
14
14
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
15
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
15
|
+
<link rel="stylesheet" href="/styles.css?v=59" />
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<button id="sidePanelExpandButton" class="side-panel-expand-button" type="button" aria-controls="sidePanel" aria-expanded="false" aria-label="Expand side panel" title="Expand side panel">
|
|
@@ -740,6 +740,6 @@
|
|
|
740
740
|
</form>
|
|
741
741
|
</dialog>
|
|
742
742
|
|
|
743
|
-
<script type="module" src="/app.js?v=
|
|
743
|
+
<script type="module" src="/app.js?v=57"></script>
|
|
744
744
|
</body>
|
|
745
745
|
</html>
|
package/public/styles.css
CHANGED
|
@@ -74,7 +74,10 @@
|
|
|
74
74
|
|
|
75
75
|
* { box-sizing: border-box; }
|
|
76
76
|
html, body { height: 100%; min-height: 100%; }
|
|
77
|
-
html {
|
|
77
|
+
html {
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
scrollbar-gutter: auto;
|
|
80
|
+
}
|
|
78
81
|
body {
|
|
79
82
|
margin: 0;
|
|
80
83
|
min-height: 0;
|
|
@@ -2187,6 +2190,10 @@ button.footer-meta {
|
|
|
2187
2190
|
max-height: none;
|
|
2188
2191
|
overflow: visible;
|
|
2189
2192
|
}
|
|
2193
|
+
.footer-model-picker.footer-branch-picker {
|
|
2194
|
+
overflow: visible;
|
|
2195
|
+
max-height: none;
|
|
2196
|
+
}
|
|
2190
2197
|
.footer-branch-picker {
|
|
2191
2198
|
width: min(28rem, calc(100vw - 2rem));
|
|
2192
2199
|
border-color: rgba(245, 194, 231, 0.30);
|
|
@@ -2210,6 +2217,35 @@ button.footer-meta {
|
|
|
2210
2217
|
border-color: rgba(166, 227, 161, 0.46);
|
|
2211
2218
|
box-shadow: inset 3px 0 0 var(--ctp-green), 0 0 1rem rgba(166, 227, 161, 0.14);
|
|
2212
2219
|
}
|
|
2220
|
+
.footer-model-option[data-footer-model-key] {
|
|
2221
|
+
position: relative;
|
|
2222
|
+
cursor: grab;
|
|
2223
|
+
touch-action: none;
|
|
2224
|
+
user-select: none;
|
|
2225
|
+
}
|
|
2226
|
+
.footer-model-option.dragging {
|
|
2227
|
+
cursor: grabbing;
|
|
2228
|
+
border-color: rgba(148, 226, 213, 0.72);
|
|
2229
|
+
box-shadow: inset 0 0 0 1px rgba(148, 226, 213, 0.46), inset 4px 0 0 var(--ctp-teal), 0 0 1.1rem rgba(148, 226, 213, 0.24);
|
|
2230
|
+
}
|
|
2231
|
+
.footer-model-option.drag-over-before::before,
|
|
2232
|
+
.footer-model-option.drag-over-after::after {
|
|
2233
|
+
content: "";
|
|
2234
|
+
position: absolute;
|
|
2235
|
+
left: 0.62rem;
|
|
2236
|
+
right: 0.62rem;
|
|
2237
|
+
height: 2px;
|
|
2238
|
+
border-radius: 999px;
|
|
2239
|
+
background: var(--ctp-teal);
|
|
2240
|
+
box-shadow: 0 0 0.7rem rgba(148, 226, 213, 0.72);
|
|
2241
|
+
pointer-events: none;
|
|
2242
|
+
}
|
|
2243
|
+
.footer-model-option.drag-over-before::before {
|
|
2244
|
+
top: -0.28rem;
|
|
2245
|
+
}
|
|
2246
|
+
.footer-model-option.drag-over-after::after {
|
|
2247
|
+
bottom: -0.28rem;
|
|
2248
|
+
}
|
|
2213
2249
|
.footer-branch-option.active {
|
|
2214
2250
|
border-color: rgba(245, 194, 231, 0.36);
|
|
2215
2251
|
box-shadow: inset 3px 0 0 var(--ctp-pink), 0 0 1rem rgba(245, 194, 231, 0.12);
|
|
@@ -2225,6 +2261,192 @@ button.footer-meta {
|
|
|
2225
2261
|
.footer-branch-create-option .footer-model-option-main {
|
|
2226
2262
|
color: var(--ctp-green);
|
|
2227
2263
|
}
|
|
2264
|
+
.footer-branch-create-form {
|
|
2265
|
+
display: grid;
|
|
2266
|
+
gap: 0.44rem;
|
|
2267
|
+
padding: 0.6rem 0.64rem;
|
|
2268
|
+
border: 1px solid rgba(166, 227, 161, 0.28);
|
|
2269
|
+
border-radius: 0.78rem;
|
|
2270
|
+
background: rgba(166, 227, 161, 0.06);
|
|
2271
|
+
box-shadow: inset 3px 0 0 rgba(166, 227, 161, 0.72);
|
|
2272
|
+
}
|
|
2273
|
+
.footer-branch-create-header {
|
|
2274
|
+
display: flex;
|
|
2275
|
+
align-items: center;
|
|
2276
|
+
justify-content: space-between;
|
|
2277
|
+
gap: 0.6rem;
|
|
2278
|
+
}
|
|
2279
|
+
.footer-branch-create-title {
|
|
2280
|
+
color: var(--ctp-green);
|
|
2281
|
+
font-size: 0.78rem;
|
|
2282
|
+
font-weight: 900;
|
|
2283
|
+
}
|
|
2284
|
+
.footer-branch-create-preview {
|
|
2285
|
+
color: rgba(var(--ctp-subtext-rgb), 0.72);
|
|
2286
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
2287
|
+
font-size: 0.7rem;
|
|
2288
|
+
}
|
|
2289
|
+
.footer-branch-create-fields {
|
|
2290
|
+
display: grid;
|
|
2291
|
+
grid-template-columns: minmax(5.4rem, 0.34fr) auto minmax(0, 1fr) auto;
|
|
2292
|
+
align-items: center;
|
|
2293
|
+
gap: 0.4rem;
|
|
2294
|
+
}
|
|
2295
|
+
.footer-branch-create-type-field {
|
|
2296
|
+
position: relative;
|
|
2297
|
+
min-width: 0;
|
|
2298
|
+
}
|
|
2299
|
+
.footer-branch-create-type-field::after {
|
|
2300
|
+
content: "▾";
|
|
2301
|
+
position: absolute;
|
|
2302
|
+
top: 50%;
|
|
2303
|
+
right: 0.5rem;
|
|
2304
|
+
transform: translateY(-50%);
|
|
2305
|
+
color: var(--ctp-green);
|
|
2306
|
+
font-size: 0.72rem;
|
|
2307
|
+
pointer-events: none;
|
|
2308
|
+
opacity: 0.86;
|
|
2309
|
+
}
|
|
2310
|
+
.footer-branch-create-dropdown-inputfield,
|
|
2311
|
+
.footer-branch-create-input-field {
|
|
2312
|
+
min-width: 0;
|
|
2313
|
+
min-height: 2.28rem;
|
|
2314
|
+
border: 1px solid rgba(var(--ctp-overlay-rgb), 0.38);
|
|
2315
|
+
border-radius: 0.62rem;
|
|
2316
|
+
background: rgba(var(--ctp-crust-rgb), 0.56);
|
|
2317
|
+
color: var(--ctp-text);
|
|
2318
|
+
font: inherit;
|
|
2319
|
+
}
|
|
2320
|
+
.footer-branch-create-dropdown-inputfield {
|
|
2321
|
+
width: 100%;
|
|
2322
|
+
padding: 0 1.35rem 0 0.48rem;
|
|
2323
|
+
}
|
|
2324
|
+
.footer-branch-create-slash {
|
|
2325
|
+
color: var(--ctp-green);
|
|
2326
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
2327
|
+
font-size: 1rem;
|
|
2328
|
+
font-weight: 900;
|
|
2329
|
+
opacity: 0.9;
|
|
2330
|
+
}
|
|
2331
|
+
.footer-branch-type-suggestions {
|
|
2332
|
+
position: absolute;
|
|
2333
|
+
left: 0;
|
|
2334
|
+
top: calc(100% + 0.26rem);
|
|
2335
|
+
z-index: 60;
|
|
2336
|
+
display: grid;
|
|
2337
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2338
|
+
gap: 0.18rem;
|
|
2339
|
+
width: min(20rem, calc(100vw - 2rem));
|
|
2340
|
+
max-height: none;
|
|
2341
|
+
overflow: visible;
|
|
2342
|
+
padding: 0.32rem;
|
|
2343
|
+
border: 1px solid rgba(166, 227, 161, 0.32);
|
|
2344
|
+
border-radius: 0.66rem;
|
|
2345
|
+
background: linear-gradient(145deg, rgba(var(--ctp-crust-rgb), 0.99), rgba(var(--ctp-base-rgb), 0.98));
|
|
2346
|
+
box-shadow: 0 0.9rem 1.8rem rgba(var(--ctp-crust-rgb), 0.58), 0 0 1rem rgba(166, 227, 161, 0.12);
|
|
2347
|
+
}
|
|
2348
|
+
.footer-branch-type-suggestions[hidden] {
|
|
2349
|
+
display: none !important;
|
|
2350
|
+
}
|
|
2351
|
+
.footer-branch-type-suggestion {
|
|
2352
|
+
min-height: 1.85rem;
|
|
2353
|
+
padding: 0.28rem 0.48rem;
|
|
2354
|
+
border-color: rgba(var(--ctp-overlay-rgb), 0.22);
|
|
2355
|
+
border-radius: 0.48rem;
|
|
2356
|
+
color: var(--ctp-text);
|
|
2357
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
2358
|
+
font-size: 0.76rem;
|
|
2359
|
+
text-align: left;
|
|
2360
|
+
}
|
|
2361
|
+
.footer-branch-type-suggestion:hover,
|
|
2362
|
+
.footer-branch-type-suggestion:focus,
|
|
2363
|
+
.footer-branch-type-suggestion.active {
|
|
2364
|
+
border-color: rgba(166, 227, 161, 0.48);
|
|
2365
|
+
color: var(--ctp-green);
|
|
2366
|
+
background: rgba(166, 227, 161, 0.10);
|
|
2367
|
+
}
|
|
2368
|
+
.footer-branch-create-input-field {
|
|
2369
|
+
padding: 0 0.68rem;
|
|
2370
|
+
}
|
|
2371
|
+
.footer-branch-create-submit {
|
|
2372
|
+
position: relative;
|
|
2373
|
+
min-height: 2.28rem;
|
|
2374
|
+
padding: 0 0.72rem;
|
|
2375
|
+
border-color: rgba(166, 227, 161, 0.42);
|
|
2376
|
+
color: var(--ctp-green);
|
|
2377
|
+
white-space: nowrap;
|
|
2378
|
+
}
|
|
2379
|
+
.footer-branch-create-submit-disabled {
|
|
2380
|
+
cursor: not-allowed;
|
|
2381
|
+
border-color: rgba(166, 227, 161, 0.24);
|
|
2382
|
+
color: rgba(166, 227, 161, 0.58);
|
|
2383
|
+
background: rgba(var(--ctp-crust-rgb), 0.42);
|
|
2384
|
+
}
|
|
2385
|
+
.footer-branch-create-submit[data-tooltip]::before,
|
|
2386
|
+
.footer-branch-create-submit[data-tooltip]::after {
|
|
2387
|
+
position: absolute;
|
|
2388
|
+
z-index: 80;
|
|
2389
|
+
opacity: 0;
|
|
2390
|
+
pointer-events: none;
|
|
2391
|
+
transition: opacity 0.14s ease, transform 0.14s ease;
|
|
2392
|
+
}
|
|
2393
|
+
.footer-branch-create-submit[data-tooltip]::before {
|
|
2394
|
+
content: "";
|
|
2395
|
+
right: 1.2rem;
|
|
2396
|
+
bottom: calc(100% + 0.38rem);
|
|
2397
|
+
width: 0.72rem;
|
|
2398
|
+
height: 0.72rem;
|
|
2399
|
+
border: 1px solid rgba(166, 227, 161, 0.26);
|
|
2400
|
+
border-top: 0;
|
|
2401
|
+
border-left: 0;
|
|
2402
|
+
background: rgba(var(--ctp-crust-rgb), 0.99);
|
|
2403
|
+
transform: translateY(0.24rem) rotate(45deg);
|
|
2404
|
+
}
|
|
2405
|
+
.footer-branch-create-submit[data-tooltip]::after {
|
|
2406
|
+
content: attr(data-tooltip);
|
|
2407
|
+
right: 0;
|
|
2408
|
+
bottom: calc(100% + 0.7rem);
|
|
2409
|
+
width: min(24rem, calc(100vw - 2.4rem));
|
|
2410
|
+
padding: 0.72rem 0.78rem;
|
|
2411
|
+
border: 1px solid rgba(166, 227, 161, 0.30);
|
|
2412
|
+
border-radius: 0.78rem;
|
|
2413
|
+
background:
|
|
2414
|
+
radial-gradient(circle at 10% 0%, rgba(166, 227, 161, 0.13), transparent 10rem),
|
|
2415
|
+
linear-gradient(145deg, rgba(var(--ctp-crust-rgb), 0.99), rgba(var(--ctp-base-rgb), 0.98));
|
|
2416
|
+
box-shadow: 0 1rem 2rem rgba(var(--ctp-crust-rgb), 0.62), 0 0 1.2rem rgba(166, 227, 161, 0.14);
|
|
2417
|
+
color: var(--ctp-text);
|
|
2418
|
+
font-size: 0.76rem;
|
|
2419
|
+
font-weight: 650;
|
|
2420
|
+
line-height: 1.42;
|
|
2421
|
+
text-align: left;
|
|
2422
|
+
white-space: pre-wrap;
|
|
2423
|
+
overflow-wrap: anywhere;
|
|
2424
|
+
transform: translateY(0.24rem);
|
|
2425
|
+
}
|
|
2426
|
+
.footer-branch-create-submit[data-tooltip]:hover::before,
|
|
2427
|
+
.footer-branch-create-submit[data-tooltip]:hover::after,
|
|
2428
|
+
.footer-branch-create-submit[data-tooltip]:focus-visible::before,
|
|
2429
|
+
.footer-branch-create-submit[data-tooltip]:focus-visible::after {
|
|
2430
|
+
opacity: 1;
|
|
2431
|
+
transform: translateY(0);
|
|
2432
|
+
}
|
|
2433
|
+
.footer-branch-create-submit[data-tooltip]:hover::before,
|
|
2434
|
+
.footer-branch-create-submit[data-tooltip]:focus-visible::before {
|
|
2435
|
+
transform: translateY(0) rotate(45deg);
|
|
2436
|
+
}
|
|
2437
|
+
@media (max-width: 520px) {
|
|
2438
|
+
.footer-branch-create-fields {
|
|
2439
|
+
grid-template-columns: 1fr auto 1fr;
|
|
2440
|
+
}
|
|
2441
|
+
.footer-branch-type-suggestions {
|
|
2442
|
+
width: calc(100vw - 3rem);
|
|
2443
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2444
|
+
}
|
|
2445
|
+
.footer-branch-create-submit {
|
|
2446
|
+
grid-column: 1 / -1;
|
|
2447
|
+
width: 100%;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2228
2450
|
.footer-model-option-main,
|
|
2229
2451
|
.footer-model-option-name {
|
|
2230
2452
|
min-width: 0;
|
|
@@ -3410,6 +3632,14 @@ button.git-workflow-step:hover:not(:disabled) {
|
|
|
3410
3632
|
list-style: none;
|
|
3411
3633
|
}
|
|
3412
3634
|
.todo-widget-summary::-webkit-details-marker { display: none; }
|
|
3635
|
+
.todo-widget-goal {
|
|
3636
|
+
min-width: 0;
|
|
3637
|
+
color: rgba(var(--ctp-text-rgb), 0.92);
|
|
3638
|
+
font-size: 0.78rem;
|
|
3639
|
+
font-weight: 800;
|
|
3640
|
+
line-height: 1.3;
|
|
3641
|
+
overflow-wrap: anywhere;
|
|
3642
|
+
}
|
|
3413
3643
|
.todo-widget-header {
|
|
3414
3644
|
display: flex;
|
|
3415
3645
|
align-items: center;
|
|
@@ -4126,6 +4356,9 @@ button.git-workflow-step:hover:not(:disabled) {
|
|
|
4126
4356
|
position: relative;
|
|
4127
4357
|
margin: 0.55rem 0;
|
|
4128
4358
|
}
|
|
4359
|
+
.markdown-code-block.has-code-copy-action > .code-block {
|
|
4360
|
+
padding-top: 2.05rem;
|
|
4361
|
+
}
|
|
4129
4362
|
.markdown-code-language {
|
|
4130
4363
|
position: absolute;
|
|
4131
4364
|
top: 0.42rem;
|
|
@@ -4138,10 +4371,82 @@ button.git-workflow-step:hover:not(:disabled) {
|
|
|
4138
4371
|
letter-spacing: 0.08em;
|
|
4139
4372
|
text-transform: uppercase;
|
|
4140
4373
|
}
|
|
4374
|
+
.markdown-code-block.has-code-copy-action > .markdown-code-language {
|
|
4375
|
+
right: 4.65rem;
|
|
4376
|
+
}
|
|
4377
|
+
.markdown-code-copy-button {
|
|
4378
|
+
position: absolute;
|
|
4379
|
+
top: 0.36rem;
|
|
4380
|
+
right: 0.42rem;
|
|
4381
|
+
z-index: 2;
|
|
4382
|
+
min-width: 3.45rem;
|
|
4383
|
+
min-height: 1.52rem;
|
|
4384
|
+
padding: 0.16rem 0.45rem;
|
|
4385
|
+
border: 1px solid rgba(148, 226, 213, 0.28);
|
|
4386
|
+
border-radius: 999px;
|
|
4387
|
+
color: rgba(var(--ctp-text-rgb), 0.82);
|
|
4388
|
+
background: rgba(var(--ctp-crust-rgb), 0.78);
|
|
4389
|
+
box-shadow: 0 0 0.7rem rgba(var(--ctp-crust-rgb), 0.28);
|
|
4390
|
+
font-size: 0.68rem;
|
|
4391
|
+
font-weight: 900;
|
|
4392
|
+
letter-spacing: 0.05em;
|
|
4393
|
+
text-transform: uppercase;
|
|
4394
|
+
opacity: 0.78;
|
|
4395
|
+
}
|
|
4396
|
+
.markdown-code-copy-button:hover,
|
|
4397
|
+
.markdown-code-copy-button:focus-visible,
|
|
4398
|
+
.markdown-code-copy-button.copied {
|
|
4399
|
+
color: #11111b;
|
|
4400
|
+
border-color: transparent;
|
|
4401
|
+
background: linear-gradient(120deg, var(--ctp-teal), var(--ctp-blue));
|
|
4402
|
+
opacity: 1;
|
|
4403
|
+
}
|
|
4404
|
+
.markdown-code-copy-button.copied {
|
|
4405
|
+
background: linear-gradient(120deg, var(--ctp-green), var(--ctp-teal));
|
|
4406
|
+
}
|
|
4141
4407
|
.markdown-code code {
|
|
4142
4408
|
display: block;
|
|
4143
4409
|
min-width: max-content;
|
|
4144
4410
|
}
|
|
4411
|
+
.markdown-mermaid-block {
|
|
4412
|
+
overflow-x: auto;
|
|
4413
|
+
padding: 0.72rem;
|
|
4414
|
+
border: 1px solid rgba(148, 226, 213, 0.22);
|
|
4415
|
+
border-radius: 0.9rem;
|
|
4416
|
+
background:
|
|
4417
|
+
radial-gradient(circle at 0 0, rgba(148, 226, 213, 0.10), transparent 18rem),
|
|
4418
|
+
rgba(var(--ctp-crust-rgb), 0.58);
|
|
4419
|
+
box-shadow: inset 0 0 1.3rem rgba(var(--ctp-crust-rgb), 0.24), 0 0 1rem rgba(148, 226, 213, 0.06);
|
|
4420
|
+
}
|
|
4421
|
+
.markdown-mermaid-diagram {
|
|
4422
|
+
display: flex;
|
|
4423
|
+
align-items: center;
|
|
4424
|
+
justify-content: center;
|
|
4425
|
+
min-width: min-content;
|
|
4426
|
+
min-height: 3.5rem;
|
|
4427
|
+
padding: 0.95rem 0.35rem 0.35rem;
|
|
4428
|
+
}
|
|
4429
|
+
.markdown-mermaid-diagram svg {
|
|
4430
|
+
display: block;
|
|
4431
|
+
max-width: 100%;
|
|
4432
|
+
height: auto;
|
|
4433
|
+
}
|
|
4434
|
+
.markdown-mermaid-status {
|
|
4435
|
+
margin: 0.35rem 0 0;
|
|
4436
|
+
font-size: 0.82rem;
|
|
4437
|
+
}
|
|
4438
|
+
.markdown-mermaid-status.error {
|
|
4439
|
+
color: var(--danger);
|
|
4440
|
+
}
|
|
4441
|
+
.markdown-mermaid-source {
|
|
4442
|
+
margin: 0.55rem 0 0;
|
|
4443
|
+
padding: 0.5rem;
|
|
4444
|
+
border-color: rgba(249, 226, 175, 0.18);
|
|
4445
|
+
background: rgba(var(--ctp-crust-rgb), 0.50);
|
|
4446
|
+
}
|
|
4447
|
+
.markdown-mermaid-source .code-block {
|
|
4448
|
+
margin-bottom: 0;
|
|
4449
|
+
}
|
|
4145
4450
|
.markdown-list {
|
|
4146
4451
|
margin: 0.45rem 0 0.55rem 1.2rem;
|
|
4147
4452
|
padding-left: 1rem;
|
|
@@ -7070,6 +7375,7 @@ button.composer-skill-tag:focus-visible {
|
|
|
7070
7375
|
padding-bottom: 0.42rem;
|
|
7071
7376
|
}
|
|
7072
7377
|
.todo-widget-summary { gap: 0.24rem; }
|
|
7378
|
+
.todo-widget-goal { font-size: 0.72rem; }
|
|
7073
7379
|
.todo-widget-header { gap: 0.26rem; }
|
|
7074
7380
|
.todo-widget-toggle {
|
|
7075
7381
|
width: 0.9rem;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { chmod, mkdtemp, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { chmod, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { networkInterfaces, tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -125,6 +125,20 @@ try {
|
|
|
125
125
|
assert.equal(gzipResponse.headers.get("content-encoding"), "gzip", "styles.css should fall back to gzip");
|
|
126
126
|
await gzipResponse.arrayBuffer();
|
|
127
127
|
|
|
128
|
+
const mermaidModuleResponse = await fetch(`http://127.0.0.1:${port}/vendor/mermaid/mermaid.esm.min.mjs`, {
|
|
129
|
+
signal: AbortSignal.timeout(5_000),
|
|
130
|
+
});
|
|
131
|
+
assert.equal(mermaidModuleResponse.status, 200, "Mermaid ESM module should be served from the vendored dependency path");
|
|
132
|
+
assert.match(mermaidModuleResponse.headers.get("content-type") || "", /text\/javascript/, "Mermaid ESM module should use a JavaScript MIME type");
|
|
133
|
+
const mermaidModuleText = await mermaidModuleResponse.text();
|
|
134
|
+
const mermaidChunkPath = mermaidModuleText.match(/\.\/(chunks\/mermaid\.esm\.min\/[A-Za-z0-9._-]+\.mjs)/)?.[1];
|
|
135
|
+
assert.ok(mermaidChunkPath, "Mermaid ESM module should reference same-directory chunks");
|
|
136
|
+
const mermaidChunkResponse = await fetch(`http://127.0.0.1:${port}/vendor/mermaid/${mermaidChunkPath}`, {
|
|
137
|
+
signal: AbortSignal.timeout(5_000),
|
|
138
|
+
});
|
|
139
|
+
assert.equal(mermaidChunkResponse.status, 200, "Mermaid ESM chunks should be served for dynamic imports");
|
|
140
|
+
assert.equal(await mermaidChunkResponse.text(), await readFile(join(root, "node_modules", "mermaid", "dist", mermaidChunkPath), "utf8"), "served Mermaid chunks should match the dependency files");
|
|
141
|
+
|
|
128
142
|
const tabsResponse = await request("127.0.0.1", "/api/tabs");
|
|
129
143
|
assert.equal(tabsResponse.status, 200);
|
|
130
144
|
const tabList = tabsResponse.body?.data?.tabs || tabsResponse.body?.tabs || [];
|
|
@@ -214,7 +214,7 @@ assert.match(app, /function handleRemoteWebuiStatus\(statusText\)[\s\S]*?opening
|
|
|
214
214
|
assert.match(app, /case "confirm":[\s\S]*?if \(isRemoteWebuiQrPopupLoading\(\)\) closeRemoteWebuiQrPopup\(\)/, "blocking extension dialogs should close the QR loading popup before opening");
|
|
215
215
|
assert.match(app, /function showRemoteWebuiQrPopup\(widgetKey, lines = \[\], request = \{\}\)[\s\S]*?widgetKey !== "pi-remote-webui"[\s\S]*?openRemoteWebuiQrPopup\(lines\)/, "remote WebUI QR widget events should open the QR popup");
|
|
216
216
|
assert.match(app, /function mirrorRemoteWebuiWidgetToTranscript\(widgetKey, lines = \[\], request = \{\}\)[\s\S]*?widgetKey !== "pi-remote-webui"[\s\S]*?addTransientMessage\(\{ role: "extension", title: "\/remote"/, "remote WebUI QR widget events should still mirror into the active tab transcript");
|
|
217
|
-
assert.match(app, /if \(widgetKey === "pi-remote-webui"\) \{[\s\S]*?
|
|
217
|
+
assert.match(app, /if \(widgetKey === "pi-remote-webui"\) \{[\s\S]*?setWidgetForTab\(requestTabId, widgetKey, \{ \.\.\.request, widgetLines: undefined \}\);[\s\S]*?showRemoteWebuiQrPopup\(widgetKey, request\.widgetLines, request\)/, "remote WebUI QR widget events should not render in the generic widget area");
|
|
218
218
|
assert.doesNotMatch(app, /function renderRemoteWebuiWidget/, "remote WebUI QR should not render through the generic widget renderer");
|
|
219
219
|
assert.match(css, /\.message\.run-indicator-message \{[\s\S]*?border-color/, "active agent runs should render a visible transcript indicator card");
|
|
220
220
|
assert.match(css, /\.message-copy-button \{[\s\S]*?position:\s*absolute/, "transcript messages should expose a top-right copy button");
|
|
@@ -250,6 +250,7 @@ assert.match(css, /\.side-panel-section:not\(\.collapsed\) \.side-panel-section-
|
|
|
250
250
|
assert.match(css, /\.optional-feature-pill\.enabled/, "optional features should visually distinguish enabled state");
|
|
251
251
|
assert.match(css, /\.todo-widget \{[\s\S]*?display:\s*grid/, "todo-progress widget should render as a styled checklist card");
|
|
252
252
|
assert.match(css, /\.todo-widget-summary \{[\s\S]*?cursor:\s*pointer/, "todo-progress widget should expose a compact expandable summary");
|
|
253
|
+
assert.match(css, /\.todo-widget-goal \{[\s\S]*?overflow-wrap:\s*anywhere/, "todo-progress widget should show long goals above progress without layout overflow");
|
|
253
254
|
assert.match(css, /\.todo-widget-body \{[\s\S]*?max-height:/, "expanded todo-progress details should be height-limited");
|
|
254
255
|
assert.match(css, /\.todo-widget-item\.partial \.todo-widget-marker/, "todo-progress partial items should have distinct styling");
|
|
255
256
|
assert.match(css, /\.todo-widget-item\.done \.todo-widget-text[\s\S]*?text-decoration:\s*line-through/, "todo-progress completed items should be visually crossed out");
|
|
@@ -519,6 +520,12 @@ assert.match(app, /function messageCopyText\(message, body = null\)/, "frontend
|
|
|
519
520
|
assert.match(app, /function attachMessageCopyButton\(bubble, message, body\)/, "frontend should add copy controls to rendered transcript cards");
|
|
520
521
|
assert.match(app, /button\.append\(make\("span", "message-copy-icon", "⧉"\)\)/, "message copy buttons should render as icon-only controls");
|
|
521
522
|
assert.match(app, /copyMessageBubble\(button\)/, "message copy buttons should copy through the shared clipboard helper");
|
|
523
|
+
assert.match(app, /function attachMarkdownCodeCopyButton\(wrapper, label = "Copy"\)/, "frontend should add copy controls to markdown code blocks");
|
|
524
|
+
assert.match(app, /function copyMarkdownCodeBlock\(button\)[\s\S]*?await copyText\(text\)/, "markdown code block copy controls should use the shared clipboard helper");
|
|
525
|
+
assert.match(app, /attachMarkdownCodeCopyButton\(wrapper\);/, "normal fenced code blocks should get copy buttons");
|
|
526
|
+
assert.match(app, /attachMarkdownCodeCopyButton\(wrapper, "Copy source"\);/, "rendered Mermaid blocks should expose source copy buttons");
|
|
527
|
+
assert.match(css, /\.markdown-code-copy-button \{[\s\S]*?position:\s*absolute/, "markdown code blocks should expose positioned copy buttons");
|
|
528
|
+
assert.match(css, /\.markdown-code-block\.has-code-copy-action > \.code-block \{[\s\S]*?padding-top:/, "code block copy buttons should reserve vertical space above source text");
|
|
522
529
|
assert.match(app, /retryServerConnectionButton.*retryServerConnection/s, "backend-offline recovery panel should wire a retry action");
|
|
523
530
|
assert.match(app, /function isChatNearBottom\(/, "chat should detect whether the user is reading above the bottom");
|
|
524
531
|
assert.match(app, /function scheduleChatFollowScroll\(/, "chat auto-follow should retry after layout settles during fast streaming");
|
|
@@ -545,6 +552,8 @@ assert.match(app, /case "webui_extension_ui_cancelled":/, "frontend should close
|
|
|
545
552
|
assert.match(app, /case "webui_extension_ui_resolved":[\s\S]*?removeQueuedDialogRequests\(\[event\.id\]\)/, "frontend should close dialogs resolved by another connected browser");
|
|
546
553
|
assert.match(app, /if \(responseId && activeDialog && String\(activeDialog\.id \|\| ""\) !== responseId\) return;/, "dialog response cleanup should not close the next queued dialog after a resolve-event race");
|
|
547
554
|
assert.match(app, /function parseTodoProgressWidget\(lines\)/, "todo-progress widgets should be parsed from extension widget lines");
|
|
555
|
+
assert.ok(app.includes("const goalLine = cleanLines.find((line) => /^Goal\\s*[::]/i.test(line));"), "todo-progress parser should preserve an optional Goal line from extension widget lines");
|
|
556
|
+
assert.ok(app.includes("if (todo.goal) summary.append(make(\"div\", \"todo-widget-goal\", `Goal: ${todo.goal}`));"), "todo-progress widget should display the goal above the progress header");
|
|
548
557
|
assert.match(app, /const todoProgressWidgetExpandedByTab = new Map\(\)/, "todo-progress expansion state should survive widget re-renders per tab");
|
|
549
558
|
assert.match(app, /const node = make\("details", "widget todo-widget"\)/, "todo-progress widget should render collapsed by default as expandable details");
|
|
550
559
|
assert.match(app, /Optional feature detection intentionally checks loaded Pi capabilities/, "optional Web UI features should be detected through loaded capabilities, not package folders");
|
|
@@ -599,8 +608,22 @@ assert.match(app, /function insertChangedFilePathReference\(path\)[\s\S]*formatP
|
|
|
599
608
|
assert.match(app, /function renderGitFooterPayloadMeta\(chip, tab\)[\s\S]*options\.title = gitFooterPayloadTooltip\(chip, \{ action \}\)[\s\S]*footerMeta\(chip\.label, chip\.value, footerMetaClassForPayload\(chip\), options\)[\s\S]*applyFooterChangedFilesDropdown\(node, chip\)/, "git footer meta payload chips should render as styled metadata with explanatory tooltips and changes popovers");
|
|
600
609
|
assert.match(app, /chip\.key === "git"[\s\S]*setFooterBranchPickerOpen\(!footerBranchPickerOpen\)[\s\S]*Click to switch to another local branch/, "git branch footer chip should open the branch picker");
|
|
601
610
|
assert.match(app, /function renderFooterBranchPicker\(\)[\s\S]*Git branches[\s\S]*applyFooterGitBranch\(branch\.name\)/, "git branch picker should render available branches and switch on selection");
|
|
602
|
-
assert.match(app, /
|
|
603
|
-
assert.match(app, /
|
|
611
|
+
assert.match(app, /function renderFooterBranchPicker\(\)[\s\S]*renderFooterBranchCreateForm\(state\)[\s\S]*for \(const branch of state\.branches\)/, "git branch picker should always offer inline branch creation before local branch choices");
|
|
612
|
+
assert.match(app, /GIT_BRANCH_TYPE_SUGGESTIONS = \["feat", "fix", "change", "perf", "test", "chore", "refactor", "docs", "style", "build", "ci", "revert"\]/, "new branch creation should reuse the conventional type suggestions from /git-staged-msg");
|
|
613
|
+
assert.match(app, /function renderFooterBranchCreateForm\(state = footerBranchPickerState\)[\s\S]*footer-branch-create-dropdown-inputfield[\s\S]*footer-branch-type-suggestions[\s\S]*footer-branch-create-input-field[\s\S]*Create new branch/, "new branch creation should use a styled editable suggestions dropdown plus input field instead of a browser prompt");
|
|
614
|
+
assert.match(app, /let footerBranchCreateDraft = \{ type: "", name: "" \}/, "new branch creation should not default the editable type prefix to feat");
|
|
615
|
+
assert.match(app, /function footerBranchCreateType\(value = footerBranchCreateDraft\.type\) \{\n\s+return slugifyGitBranchPart\(value\);\n\}/, "branch type suggestions should not restrict or default custom user-entered prefixes");
|
|
616
|
+
assert.match(app, /const slash = make\("span", "footer-branch-create-slash", "\/"\)/, "branch creation should visibly separate the prefix and user input with a slash");
|
|
617
|
+
assert.match(app, /function gitSwitchCreateCommandDisplay\(branch\)[\s\S]*`git switch -c \$\{quoteGitBranchForDisplay\(branch\)\}`[\s\S]*preview\.textContent = gitSwitchCreateCommandDisplay\(branchName \|\| footerBranchCreatePreviewName\(\)\)/, "branch creation should show the live complete quoted git switch -c command");
|
|
618
|
+
assert.match(app, /function footerBranchCreateTooltip\(branchName = footerBranchCreateName\(\)\)[\s\S]*A branch is a safe workspace for your changes\.[\s\S]*does not commit, push, or delete anything[\s\S]*Tip: use short lowercase words/, "create branch button should have a detailed non-technical explanatory tooltip");
|
|
619
|
+
assert.match(app, /submitButton\.dataset\.tooltip = footerBranchCreateTooltip\(branchName\);[\s\S]*submitButton\.removeAttribute\("title"\)/, "create branch button should use the styled tooltip instead of a native title tooltip");
|
|
620
|
+
assert.match(css, /\.footer-branch-create-submit\[data-tooltip\]::after \{[\s\S]*?content:\s*attr\(data-tooltip\);[\s\S]*?white-space:\s*pre-wrap;[\s\S]*?overflow-wrap:\s*anywhere;/, "create branch button tooltip should be styled and support detailed multiline copy");
|
|
621
|
+
assert.match(app, /submitButton\.disabled = false;[\s\S]*submitButton\.classList\.toggle\("footer-branch-create-submit-disabled", submitDisabled\);[\s\S]*submitButton\.setAttribute\("aria-disabled", submitDisabled \? "true" : "false"\)/, "branch creation should use aria-disabled styling so the tooltip is not dimmed by disabled button opacity");
|
|
622
|
+
assert.match(css, /\.footer-branch-create-submit-disabled \{[\s\S]*?cursor:\s*not-allowed;[\s\S]*?color:\s*rgba\(166, 227, 161, 0\.58\);/, "greyed branch create button should use a class instead of disabled opacity");
|
|
623
|
+
assert.match(app, /Loading existing local branches… New branch creation is available\./, "branch picker loading copy should make clear that creation is still available");
|
|
624
|
+
assert.match(css, /\.footer-model-picker\.footer-branch-picker \{[\s\S]*?overflow:\s*visible;[\s\S]*?max-height:\s*none;/, "branch picker should not force scrolling just to see the open type suggestions dropdown");
|
|
625
|
+
assert.match(css, /\.footer-branch-type-suggestions \{[\s\S]*?grid-template-columns:\s*repeat\(3,[\s\S]*?max-height:\s*none;[\s\S]*?overflow:\s*visible;/, "branch type suggestions should render as a styled multi-column dropdown without internal scrolling");
|
|
626
|
+
assert.match(app, /async function createFooterGitBranch\(branch = footerBranchCreateName\(\)\)[\s\S]*confirmFooterGitBranchAction\(branchName, \{ create: true, requireConfirm: true/, "new branch creation should require confirmation before running git switch -c");
|
|
604
627
|
assert.match(app, /function footerBranchAgentWarningLines[\s\S]*WARNING:[\s\S]*still running or waiting for input in this Git working tree/, "branch create/switch confirmation should warn when an agent is active in the current git working tree");
|
|
605
628
|
assert.match(app, /if \(footerBranchPickerOpen\) elements\.statusBar\.append\(renderFooterBranchPicker\(\)\)/, "footer should append the branch picker above the status bar when open");
|
|
606
629
|
assert.match(server, /url\.pathname === "\/api\/git-branches"[\s\S]*readGitBranches\(tab\.cwd\)/, "server should expose local git branch listing for the footer picker");
|
|
@@ -776,6 +799,8 @@ assert.match(app, /assistantThinkingTextFromMessage\(assistantStreamingMessage\(
|
|
|
776
799
|
assert.match(app, /if \(typeof partialText === "string"\) streamRawText = partialText;/, "live assistant text should synchronize from partial messages instead of relying only on deltas");
|
|
777
800
|
assert.match(app, /const TODO_PROGRESS_LINE_REGEX = /, "frontend should recognize live todo progress lines that will be moved into the todo widget");
|
|
778
801
|
assert.match(app, /function stripTodoProgressLines\(text, \{ streaming = false \} = \{\}\)/, "live Assistant output should strip todo-progress lines before rendering final-output text");
|
|
802
|
+
assert.match(app, /function syncLiveTodoProgressWidgetFromText\(text, tabId = activeTabId\)/, "live Assistant checklist text should update the todo-progress widget before tool execution events");
|
|
803
|
+
assert.match(app, /syncLiveTodoProgressWidgetFromText\(streamRawText, event\.tabId \|\| activeTabId\)/, "streaming assistant text should feed the live todo-progress widget immediately");
|
|
779
804
|
assert.match(app, /function renderStreamingAssistantText\(\)[\s\S]*?const assistantText = stripTodoProgressLines\(streamRawText, \{ streaming: true \}\)/, "streamed Assistant text should classify from accumulated output without flashing partial todo-progress lines");
|
|
780
805
|
assert.match(app, /function syncStreamingThinkingFormat\(assistantText\)[\s\S]*?splitThinkingFormatText\(assistantText, \{ streaming: true \}\)[\s\S]*?setStreamingThinkingText\(thinking\)/, "tagged <think> streaming output should update the live thinking card instead of flashing raw tags");
|
|
781
806
|
assert.match(app, /const finalText = thinkingFormat\?\.hasThinkingFormat \? stripTodoProgressLines\(thinkingFormat\.finalText, \{ streaming: true \}\) : assistantText;/, "tagged <think> streaming output should render only final response text in the Assistant card");
|