@worca/app 1.2.0-rc.2 → 1.2.0

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.
@@ -77,7 +77,7 @@ export function suggestDuplicateId(id, takenIds = []) {
77
77
  * `globals` come MASKED from GET /api/models. `predefinedShadowedIds` marks
78
78
  * built-ins currently overridden by a global entry.
79
79
  */
80
- export function renderModelsList({ globals = [], legacy = [], plugins = [], predefined = [], efforts = [], projectName = '' } = {}, { doc = globalThis.document } = {}) {
80
+ export function renderModelsList({ globals = [], legacy = [], plugins = [], predefined = [], efforts = [], hideBuiltin = false, projectName = '' } = {}, { doc = globalThis.document } = {}) {
81
81
  const root = h(doc, 'div', 'mv-list');
82
82
  const predefLc = new Set(predefined.map((m) => m.id.toLowerCase()));
83
83
  const pluginLc = new Set(plugins.map((m) => m.id.toLowerCase()));
@@ -89,6 +89,30 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
89
89
  return s;
90
90
  };
91
91
 
92
+ // The endpoint-routed badge (#422): honest disclosure that worca points the
93
+ // CLI's internal haiku/sonnet/opus/fable lookups at this model's own id
94
+ // (config.mjs#resolveModelEnv), so nothing falls back to the Anthropic API.
95
+ // The masked env still carries its KEYS, so presence is decidable here.
96
+ const routedBadge = (m) => {
97
+ if (!m.env || !Object.prototype.hasOwnProperty.call(m.env, 'ANTHROPIC_BASE_URL')) return null;
98
+ const b = h(doc, 'span', 'badge waiting mv-routed', 'endpoint-routed');
99
+ b.title = "worca points Claude Code's internal haiku/sonnet/opus/fable lookups at this model, so it never falls back to the Anthropic API.";
100
+ return b;
101
+ };
102
+
103
+ // ── Hide built-in models (#422) — one checkbox, top of the pane ──
104
+ const hideRow = h(doc, 'div', 'mv-hide-builtin-row');
105
+ const hideLabel = h(doc, 'label', 'check-row');
106
+ const hideCb = h(doc, 'input', 'mv-hide-builtin');
107
+ hideCb.type = 'checkbox';
108
+ hideCb.checked = !!hideBuiltin;
109
+ hideLabel.appendChild(hideCb);
110
+ hideLabel.appendChild(doc.createTextNode(" Hide built-in models (you don't use a first-party Anthropic account)"));
111
+ hideRow.appendChild(hideLabel);
112
+ hideRow.appendChild(h(doc, 'small', 'hint',
113
+ 'Drops the built-ins from every model picker — new pipelines, the composer, Ask Worca, title generation. Cosmetic only: a run or reference that already names one keeps working.'));
114
+ root.appendChild(hideRow);
115
+
92
116
  // ── Your models (global) ──
93
117
  const yours = section('Your models', 'Defined once, available in every project. An entry with a built-in id overrides that built-in.');
94
118
  if (!globals.length) {
@@ -102,6 +126,8 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
102
126
  head.appendChild(h(doc, 'b', 'mv-name', m.label || m.id));
103
127
  if (predefLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadow', 'overrides built-in'));
104
128
  else if (pluginLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadow', 'overrides plugin'));
129
+ const rb = routedBadge(m);
130
+ if (rb) head.appendChild(rb);
105
131
  // The §4.6 "unreliable" badge is meaningless once an override GOVERNS this
106
132
  // model's spend — and the backend only lifts the stored flag on the model's
107
133
  // next result event, so suppress it here the moment pricing is pinned.
@@ -169,6 +195,8 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
169
195
  head.appendChild(h(doc, 'b', 'mv-name', m.label || m.id));
170
196
  head.appendChild(h(doc, 'span', 'badge waiting mv-origin', `plugin: ${m.plugin}`));
171
197
  if (globalLc.has(m.id.toLowerCase())) head.appendChild(h(doc, 'span', 'badge violet mv-shadowed', 'overridden by your copy'));
198
+ const prb = routedBadge(m);
199
+ if (prb) head.appendChild(prb);
172
200
  // Same rule as a global card: a manifest-pinned price governs the spend,
173
201
  // so the §4.6 "unreliable" flag says nothing about it.
174
202
  if (m.costUnreliable && !m.cost) head.appendChild(h(doc, 'span', 'badge waiting mv-cost', 'cost not verified'));
@@ -199,8 +227,11 @@ export function renderModelsList({ globals = [], legacy = [], plugins = [], pred
199
227
  }
200
228
 
201
229
  // ── Built-ins (read-only) ──
202
- const builtins = section('Built-in models', 'Shipped with worca. Add a model with the same id to override its label, efforts, or routing.');
203
- for (const m of predefined) {
230
+ const builtins = section('Built-in models', hideBuiltin
231
+ ? `Hidden from every picker (${predefined.length} built-in${predefined.length === 1 ? '' : 's'}) — untick the box above to show them.`
232
+ : 'Shipped with worca. Add a model with the same id to override its label, efforts, or routing.');
233
+ builtins.classList.add(hideBuiltin ? 'mv-builtins-hidden' : 'mv-builtins-shown');
234
+ for (const m of hideBuiltin ? [] : predefined) {
204
235
  const row = h(doc, 'div', 'mv-builtin');
205
236
  row.dataset.id = m.id;
206
237
  row.appendChild(h(doc, 'b', 'mv-name', m.label));
@@ -959,6 +959,11 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
959
959
  /* Confirm dialog must stack ABOVE the plugin/wizard modal (equal .viewer-modal z-index,
960
960
  but #confirm-modal precedes #plugin-modal in the DOM). */
961
961
  #confirm-modal.viewer-modal{z-index:60;}
962
+ /* The folder browser is a leaf picker opened FROM other modals (add-project,
963
+ export). It precedes #export-modal in the DOM, so at equal .viewer-modal
964
+ z-index the export modal would paint over it, hiding the Browse… picker.
965
+ Elevate it above every other modal so it is always usable when open. */
966
+ #folder-browser.viewer-modal{z-index:70;}
962
967
  .viewer-modal .card{width:min(860px,100%);max-height:86vh;overflow:auto;}
963
968
  /* folder browser modal (native-dialog fallback). Must come AFTER the
964
969
  .viewer-modal rules: `.folder-modal .card` ties with `.viewer-modal .card`
@@ -1056,6 +1061,39 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1056
1061
  .pl-remove:disabled{opacity:.6;cursor:default;}
1057
1062
  .pl-remove svg{display:block;}
1058
1063
 
1064
+ /* Export dialog (JSON file / Claude Code skill / Worca plugin). The row's
1065
+ Export… button itself is a .btn-ghost (see .gv-saved .pl-export). */
1066
+ .export-field{margin:12px 0;}
1067
+ .export-field .hint{display:block;margin-top:6px;}
1068
+ /* A checkbox row directly under a text input needs its own breathing room:
1069
+ .confirm-checkbox ships margin:0 (it is built for the confirm dialog's stack). */
1070
+ .export-field .confirm-checkbox{margin-top:10px;}
1071
+ /* Result view after a successful export (replaces #export-form in the same card). */
1072
+ .export-done{margin:12px 0 4px;}
1073
+ .export-done.hidden{display:none;}
1074
+ .export-done-head{display:flex;align-items:center;gap:10px;font-size:14px;margin-bottom:12px;}
1075
+ .export-done-check{display:grid;place-items:center;width:26px;height:26px;border-radius:999px;
1076
+ background:var(--green-bg,#e6f4ea);color:var(--green-ink,#1e6b3a);font-weight:700;font-size:14px;}
1077
+ .export-done-lines{display:grid;grid-template-columns:max-content 1fr;gap:6px 14px;margin:0;font-size:12.5px;}
1078
+ .export-done-lines dt{color:var(--ink-3);font-weight:600;}
1079
+ .export-done-lines dd{margin:0;color:var(--ink);word-break:break-all;}
1080
+ .export-done-lines code{font:12px var(--mono);background:var(--field);border-radius:6px;padding:1px 6px;}
1081
+ .export-done-next{margin:14px 0 0;padding:10px 12px;border-radius:10px;background:var(--field);font-size:12.5px;color:var(--ink-2);}
1082
+ .export-done-next code{font:12px var(--mono);}
1083
+ .export-label{display:block;font-weight:600;font-size:12.5px;color:var(--ink-2);margin-bottom:6px;}
1084
+ .export-folder-row{display:flex;gap:8px;align-items:center;}
1085
+ .export-folder-row .input{flex:1 1 auto;}
1086
+ .export-plan{margin:12px 0;max-height:240px;overflow:auto;border:1px solid var(--line-2);border-radius:10px;padding:8px;font-size:12.5px;}
1087
+ .export-row{padding:4px 2px;display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
1088
+ .export-conflict-row{flex-direction:column;align-items:flex-start;}
1089
+ .export-tag{display:inline-block;min-width:58px;text-align:center;border-radius:6px;padding:1px 6px;font-weight:700;font-size:11px;}
1090
+ .export-create{background:var(--green-bg,#e6f7ec);color:var(--green-ink,#137a3b);}
1091
+ .export-update{background:var(--field);color:var(--ink);}
1092
+ .export-no-op{background:var(--field);color:var(--ink-3);}
1093
+ .export-warn,.export-conflict{background:var(--red-bg,#fde8e8);color:var(--red-ink,#b42323);}
1094
+ .export-orphan{background:var(--field);color:var(--ink-3);}
1095
+ .export-choices{display:flex;gap:12px;margin:4px 0 2px 64px;}
1096
+ .export-choice{font-size:12.5px;color:var(--ink-2);cursor:pointer;}
1059
1097
  /* Per-agent fan-out toggle in the New Pipeline rows. */
1060
1098
  .fanout-toggle {
1061
1099
  display: inline-flex;
@@ -1536,10 +1574,23 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1536
1574
  .topnav button.active .topnav-amt{color:inherit;}
1537
1575
 
1538
1576
  /* ---------- Settings cards ---------- */
1539
- /* One width/centering/gap rule for all three Settings cards. */
1577
+ /* One width/centering/gap rule for every Settings card. */
1540
1578
  .settings-card{max-width:760px;padding:22px;margin-left:auto;margin-right:auto;}
1541
1579
  .settings-card + .settings-card{margin-top:18px;}
1542
1580
 
1581
+ /* About card rows. .about-val and .about-link share specificity on one anchor, so
1582
+ .about-link must stay AFTER .about-val (test/ui-settings-about.test.mjs pins it). */
1583
+ .about-row{display:flex;align-items:baseline;gap:10px;font-size:13px;}
1584
+ .about-row + .about-row{margin-top:10px;}
1585
+ .about-key{flex:0 0 96px;font-weight:600;color:var(--ink-2);}
1586
+ .about-val{color:var(--ink);min-width:0;}
1587
+ .about-version{font-family:var(--mono);}
1588
+ /* Once painted with a release-tag href the version reads as a link. */
1589
+ .about-version[href]{color:var(--green-ink);text-decoration:none;}
1590
+ .about-version[href]:hover{text-decoration:underline;}
1591
+ .about-link{color:var(--green-ink);font-weight:600;text-decoration:none;overflow-wrap:anywhere;}
1592
+ .about-link:hover{text-decoration:underline;}
1593
+
1543
1594
  /* ---------- Budget readout (Settings) ---------- */
1544
1595
  /* Section gap before the limit fields (the disclaimer hint that used to sit
1545
1596
  here moved into the card heading's ⓘ tooltip). */
@@ -1586,6 +1637,8 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1586
1637
  /* ---- Models view (configurable-models-design.md §4.10) ---- */
1587
1638
  .mv-list{display:grid;gap:18px;}
1588
1639
  .mv-section{display:grid;gap:10px;}
1640
+ .mv-hide-builtin-row{display:grid;gap:6px;padding:10px 12px;border:1px solid var(--line-2,#e6e6ec);border-radius:10px;}
1641
+ .mv-hide-builtin-row .hint{margin:0;}
1589
1642
  .mv-section-title{margin:4px 0 0;font-size:13px;text-transform:uppercase;letter-spacing:.04em;opacity:.75;}
1590
1643
  .mv-card{padding:16px 20px;display:flex;align-items:center;gap:14px;}
1591
1644
  .mv-body{flex:1 1 auto;min-width:0;}
@@ -2820,7 +2873,8 @@ body.rail-collapsed .ask-dock{left:76px;}
2820
2873
  font-family:var(--mono);font-weight:400;font-size:10.5px;}
2821
2874
 
2822
2875
  .ask-sheet{pointer-events:auto;position:relative;display:flex;flex-direction:column;overflow:hidden;
2823
- width:min(782px,100%);height:min(669px,calc(100% - 20px));background:var(--panel);
2876
+ width:min(782px,100%);height:min(669px,calc(100% - 20px));max-width:100%;max-height:calc(100% - 20px);
2877
+ background:var(--panel);
2824
2878
  border:1px solid var(--line-2);border-radius:var(--r-card);
2825
2879
  box-shadow:0 18px 60px rgba(25,25,27,.14),0 2px 6px rgba(25,25,27,.06);
2826
2880
  animation:wr-rise .26s cubic-bezier(.2,.7,.3,1) both;}
@@ -2917,7 +2971,7 @@ body.rail-collapsed .ask-dock{left:76px;}
2917
2971
  .ask-agent-log-t{flex:0 0 auto;color:var(--seq);}
2918
2972
  .ask-agent-log-text{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;}
2919
2973
 
2920
- .ask-answer{max-width:92%;font-size:13.5px;line-height:1.72;color:var(--ink);}
2974
+ .ask-answer{max-width:100%;font-size:13.5px;line-height:1.72;color:var(--ink);}
2921
2975
  .ask-answer-plain{white-space:pre-wrap;overflow-wrap:anywhere;}
2922
2976
  .ask-md :is(h1,h2,h3,h4,h5,h6){margin:14px 0 6px;font-size:13.5px;font-weight:600;color:var(--ink);}
2923
2977
  .ask-md h1{font-size:15px;}
@@ -3105,6 +3159,41 @@ body.rail-collapsed .ask-dock{left:76px;}
3105
3159
  .ask-wt-row .ask-runinfo-sub.ask-wt-path{cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px;}
3106
3160
  .ask-wt-row .ask-thread-trash{flex:none;}
3107
3161
 
3162
+ /* Resize grips (spec §10.3 addendum). Invisible edge zones INSIDE the sheet —
3163
+ overflow:hidden would clip anything outside it — that only change the cursor;
3164
+ a 2px inset line appears on hover and stays while dragging (.is-active). No
3165
+ bottom grip: the sheet is bottom-anchored. ask-panel.mjs applies the user's
3166
+ size as inline width/height and clamps it to the dock; the max-* pair on
3167
+ .ask-sheet above is the backstop that keeps a stale inline size inside the
3168
+ dock (the rail collapsing changes the dock width without a window resize).
3169
+ The floor (JS-only, the stylesheet default 782×669: the sheet grows, never
3170
+ shrinks) is not a CSS min-* on purpose — that would overflow narrow viewports
3171
+ that width:min(782px,100%) handles today.
3172
+ Corners: the sheet clips at its rounded corner (inner radius = --r-card minus
3173
+ the 1px border), so a square corner grip's straight inset lines fall almost
3174
+ entirely outside the clip and only a ~3px sliver ever shows. Each corner is
3175
+ therefore exactly one radius wide and carries the same outer radius, so its
3176
+ inset shadow bends along the arc, flush with the border like the straight
3177
+ edges; the straight grips start where the arc ends (--ask-corner). */
3178
+ .ask-resize{position:absolute;z-index:5;background:transparent;touch-action:none;--ask-grip:transparent;
3179
+ --ask-corner:calc(var(--r-card) - 1px);transition:box-shadow .15s;}
3180
+ .ask-resize:hover{--ask-grip:var(--ink-3);}
3181
+ .ask-resize.is-active{--ask-grip:var(--ink);}
3182
+ .ask-resize-n{top:0;left:var(--ask-corner);right:var(--ask-corner);height:6px;cursor:ns-resize;box-shadow:inset 0 2px 0 var(--ask-grip);}
3183
+ .ask-resize-e{top:var(--ask-corner);right:0;bottom:var(--ask-corner);width:6px;cursor:ew-resize;box-shadow:inset -2px 0 0 var(--ask-grip);}
3184
+ .ask-resize-w{top:var(--ask-corner);left:0;bottom:var(--ask-corner);width:6px;cursor:ew-resize;box-shadow:inset 2px 0 0 var(--ask-grip);}
3185
+ .ask-resize-ne{top:0;right:0;width:var(--ask-corner);height:var(--ask-corner);cursor:nesw-resize;
3186
+ border-top-right-radius:var(--ask-corner);box-shadow:inset -2px 2px 0 var(--ask-grip);}
3187
+ .ask-resize-nw{top:0;left:0;width:var(--ask-corner);height:var(--ask-corner);cursor:nwse-resize;
3188
+ border-top-left-radius:var(--ask-corner);box-shadow:inset 2px 2px 0 var(--ask-grip);}
3189
+ .ask-sheet.is-resizing{user-select:none;}
3190
+ /* The ne corner grip (one radius square, z-index:5) overlaps the top-right
3191
+ corner of the header's last icon button (30×30 at padding 13px/14px): the
3192
+ buttons sit above the grips so a click there still closes/opens instead of
3193
+ starting a resize, and a hurried double-click never resets the size. The
3194
+ grip's visible band hugs the border, well outside the button box. */
3195
+ .ask-header .ask-icon-btn{position:relative;z-index:6;}
3196
+
3108
3197
  /* ---------- Ask Worca dock clearance ---------- */
3109
3198
  /* The dock is a fixed overlay -- .ask-dock above: 26px dock padding-bottom plus the
3110
3199
  ~47px pill occludes a ~73px strip at the bottom of every scrollport, while the page
@@ -3459,6 +3548,8 @@ input.gv-name:focus-visible{outline:none;}
3459
3548
  padding:4px 11px;font:600 11.5px var(--sans);cursor:pointer;flex:0 0 auto;white-space:nowrap;}
3460
3549
  .gv-errchip:hover{filter:brightness(.97);}
3461
3550
  .gv-errchip[hidden]{display:none;}
3551
+ /* Unfinished wiring is a to-do, not a mistake: the same chip in a quiet dress. */
3552
+ .gv-errchip.is-incomplete{background:var(--field);color:var(--ink-2);border-color:var(--line-2);}
3462
3553
  /* Toolbar density: .btn-ghost/.btn-go are PAGE-level pills (13–14px of vertical
3463
3554
  padding, 20–28px horizontal). In a 52px bar they wrap their own labels, so
3464
3555
  the bar re-states them at .btn-mini metrics — same skin, toolbar size. */
@@ -3637,11 +3728,27 @@ input.gv-name:focus-visible{outline:none;}
3637
3728
  padding:16px 20px;border-bottom:1px solid var(--line);}
3638
3729
  .saved-head b{font-size:15px;font-weight:600;letter-spacing:-.01em;}
3639
3730
  .saved-head .cnt{color:var(--ink-3);font-size:12.5px;font-weight:600;margin-left:9px;}
3731
+ .saved-head-actions{display:flex;align-items:center;gap:8px;}
3732
+ /* One tab per domain — the same pill tabs the History detail uses, one size down. */
3733
+ .gv-saved-tabs{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:12px 20px;border-bottom:1px solid var(--line);}
3734
+ .gv-saved-tabs[hidden]{display:none;}
3735
+ .gv-saved-tab{display:flex;align-items:center;gap:8px;padding:7px 13px;border:1.5px solid var(--line);
3736
+ border-radius:999px;background:var(--panel);font:600 12.5px var(--sans);color:var(--ink-2);cursor:pointer;}
3737
+ .gv-saved-tab:hover{border-color:var(--ink-3);color:var(--ink);}
3738
+ .gv-saved-tab.active{background:var(--ink);color:#fff;border-color:var(--ink);}
3739
+ .gv-saved-tab:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3740
+ .gv-saved-tab-badge{padding:1px 7px;border-radius:999px;background:var(--field);color:var(--ink-3);font:600 10.5px var(--mono);}
3741
+ .gv-saved-tab.active .gv-saved-tab-badge{background:var(--blue-bg);color:var(--blue-ink);}
3640
3742
  .saved-list{display:flex;flex-direction:column;}
3641
3743
  .gv-saved .pl-item{border-bottom:1px solid var(--line);}
3642
3744
  .gv-saved .pl-item:last-child{border-bottom:none;}
3643
3745
  .gv-saved .pl-row{display:flex;align-items:center;gap:14px;padding:12px 20px;transition:background .12s;}
3644
3746
  .gv-saved .pl-row:hover{background:var(--field);}
3747
+ /* The row IS the Open action for a v2 pipeline. */
3748
+ .gv-saved .pl-row.pl-openable{cursor:pointer;}
3749
+ .gv-saved .pl-row.pl-openable:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
3750
+ .gv-saved .pl-new{display:inline-block;margin-left:8px;padding:2px 7px;border-radius:999px;vertical-align:middle;
3751
+ background:var(--blue-bg);color:var(--blue-ink);font:700 10px var(--mono);letter-spacing:.06em;}
3645
3752
  /* The thumbnail is a fixed TILE at the head of the row: the SVG carries its own
3646
3753
  width/height, so a percentage box would leave it stranded in dead space. */
3647
3754
  .gv-saved .pl-thumb{flex:0 0 auto;width:140px;height:54px;display:grid;place-items:center;overflow:hidden;
@@ -3654,14 +3761,16 @@ input.gv-name:focus-visible{outline:none;}
3654
3761
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3655
3762
  .gv-saved .pl-meta{margin-top:4px;font-size:10.5px;font-weight:700;letter-spacing:.05em;
3656
3763
  text-transform:uppercase;color:var(--ink-3);}
3657
- .gv-saved .pl-open{flex:0 0 auto;padding:7px 15px;font-size:12.5px;}
3764
+ .gv-saved .pl-export{flex:0 0 auto;padding:7px 15px;font-size:12.5px;}
3658
3765
  .gv-saved .pl-legacy,.gv-saved .pl-origin{flex:0 0 auto;background:var(--field);border:1px solid var(--line);color:var(--ink-3);
3659
3766
  border-radius:999px;padding:3px 10px;font-size:11px;font-weight:600;}
3660
3767
  .gv-saved .pl-origin{font-family:var(--mono);letter-spacing:-.01em;}
3661
- .gv-saved .pl-del{flex:0 0 auto;width:34px;height:34px;border-radius:10px;border:1.5px solid var(--line-2);
3662
- background:#fff;display:grid;place-items:center;cursor:pointer;color:var(--ink-3);
3663
- font-family:inherit;font-size:15px;line-height:1;transition:.12s;}
3664
- .gv-saved .pl-del:hover{border-color:var(--red);color:var(--red-ink);background:var(--red-bg);}
3768
+ /* Delete: the bin icon in the pipeline Archive button's dress (.hd-archive — red
3769
+ ink on a red-bg hairline pill, fills on hover), icon-only at row-action size. */
3770
+ .gv-saved .pl-del{flex:0 0 auto;width:34px;height:34px;border-radius:999px;border:1.5px solid var(--red-bg);
3771
+ background:var(--panel);display:grid;place-items:center;cursor:pointer;color:var(--red-ink);transition:.12s;}
3772
+ .gv-saved .pl-del svg{width:15px;height:15px;stroke-width:1.9;}
3773
+ .gv-saved .pl-del:hover{border-color:var(--red);background:var(--red-bg);}
3665
3774
  .gv-saved .pl-del:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3666
3775
  .gv-saved .pl-empty{padding:30px 20px;text-align:center;color:var(--ink-3);font-size:13px;}
3667
3776
  .gv-archived{display:flex;flex-wrap:wrap;align-items:center;gap:7px;