sealkeep 0.6.2 → 0.7.1
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/CHANGELOG.md +33 -0
- package/dist/site/index.html +98 -37
- package/dist/src/doctor.js +38 -0
- package/package.json +3 -2
- package/web/setup.html +2 -2
- package/web/style.css +23 -22
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
Notable changes, by published version. Sealkeep is pre-1.0: minor versions
|
|
4
4
|
may change behavior, and say so here when they do.
|
|
5
5
|
|
|
6
|
+
## 0.7.1 — 2026-08-20
|
|
7
|
+
|
|
8
|
+
The dashboard and the account panel are one design system again.
|
|
9
|
+
|
|
10
|
+
- `design-system/tokens.css` is the single source; `npm run tokens` injects it
|
|
11
|
+
into both surfaces and `--check` fails on drift, with the suite enforcing it.
|
|
12
|
+
- The rule it encodes: monospace is what a machine said — a size, a path, a
|
|
13
|
+
state, a command — and sans is what we said. The dashboard had been running
|
|
14
|
+
every sentence in monospace because `--sans` was aliased to `--mono`, so its
|
|
15
|
+
option descriptions and standing notes read as terminal output. They read as
|
|
16
|
+
English now; every number, path and identifier is untouched.
|
|
17
|
+
|
|
18
|
+
## 0.7.0 — 2026-08-20
|
|
19
|
+
|
|
20
|
+
Two reviews — does each section deserve to exist, and can a person finish the
|
|
21
|
+
job it promises — and the answers, applied.
|
|
22
|
+
|
|
23
|
+
- **Setup no longer argues for the unrecoverable mistake.** The password path
|
|
24
|
+
said skipping the recovery phrase was "fine". Your password opens the vault on
|
|
25
|
+
that machine only; the screen now says so, names the day it matters, and shows
|
|
26
|
+
the 24 words as what survives it.
|
|
27
|
+
- The account panel leads with the Recovery key card instead of burying it ninth
|
|
28
|
+
of eleven, merges Plan into Your package (they answered the same question),
|
|
29
|
+
makes the package tiers actually start a change instead of looking clickable
|
|
30
|
+
and doing nothing, warns "Delete forever?" with the stake named, puts
|
|
31
|
+
connecting a machine above the list of machines, and stops calling an
|
|
32
|
+
account-wide upload rule "what leaves this machine".
|
|
33
|
+
- Revoked machines fold into one line — they are kept as the record, never
|
|
34
|
+
deleted, but no longer bury the machines that can actually write.
|
|
35
|
+
- `sealkeep doctor` warns when a routing rule pins a project this machine has
|
|
36
|
+
never sealed. The panel cannot check that — project names never reach us —
|
|
37
|
+
so the machine that knows does.
|
|
38
|
+
|
|
6
39
|
## 0.6.2 — 2026-08-20
|
|
7
40
|
|
|
8
41
|
- New: `sealkeep projects` lists the project names this vault knows, with how
|
package/dist/site/index.html
CHANGED
|
@@ -22,11 +22,23 @@
|
|
|
22
22
|
field on it is always, only, encrypted material.
|
|
23
23
|
--------------------------------------------------------------------- */
|
|
24
24
|
:root{
|
|
25
|
-
|
|
26
|
-
--
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--
|
|
25
|
+
/* @tokens:start — generated from design-system/tokens.css, do not edit here */
|
|
26
|
+
--paper: #f4f4f3;
|
|
27
|
+
--card: #fbfbfa;
|
|
28
|
+
--ink: #111316;
|
|
29
|
+
--soft: #5e646c;
|
|
30
|
+
--rule: #dadcde;
|
|
31
|
+
--seal: #101a3d;
|
|
32
|
+
--seal-ink: #96a6e0;
|
|
33
|
+
--live: #2a3fb0;
|
|
34
|
+
--warn: #8c3220;
|
|
35
|
+
--warn-bg: #f4e6e2;
|
|
36
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, "Helvetica Neue", sans-serif;
|
|
37
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, "DejaVu Sans Mono", Consolas, monospace;
|
|
38
|
+
/* @tokens:end */
|
|
39
|
+
|
|
40
|
+
/* Layout constant for this page's spine; not part of the shared palette. */
|
|
41
|
+
--gut: 2.6rem;
|
|
30
42
|
}
|
|
31
43
|
@media(prefers-color-scheme:dark){:root{
|
|
32
44
|
--paper:#0d0f11; --card:#14171a; --ink:#e7e9ec; --soft:#8d949c; --rule:#24282d;
|
|
@@ -216,6 +228,10 @@
|
|
|
216
228
|
.acct-nav{display:flex;gap:.35rem;flex-wrap:wrap;position:sticky;top:0;z-index:5;background:var(--paper);padding:.65rem 0;margin:.4rem 0 .2rem;border-bottom:1px solid var(--rule)}
|
|
217
229
|
.acct-nav a{font-family:var(--mono);font-size:.74rem;color:var(--soft);text-decoration:none;border:1px solid var(--rule);border-radius:999px;padding:.4rem .85rem;min-height:26px;display:inline-flex;align-items:center}
|
|
218
230
|
.acct-nav a:hover{color:var(--ink);border-color:var(--ink)}
|
|
231
|
+
#d-retired details{margin:.5rem 0 .2rem}
|
|
232
|
+
#d-retired summary{font-family:var(--mono);font-size:.76rem;color:var(--soft);cursor:pointer;padding:.35rem 0}
|
|
233
|
+
#d-retired summary:hover{color:var(--ink)}
|
|
234
|
+
#d-retired .lines li{opacity:.75}
|
|
219
235
|
.acct-hero{font-family:var(--mono);font-size:1.55rem;font-weight:700;letter-spacing:-.03em;margin:.9rem 0 0;line-height:1.3}
|
|
220
236
|
h1.acct-hero:empty{display:none}
|
|
221
237
|
.ruleslist{margin:.3rem 0 .9rem;padding-left:1.1rem}
|
|
@@ -611,6 +627,12 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
611
627
|
<span class="saved" id="a-verify-msg" role="status" aria-live="polite"></span>
|
|
612
628
|
</div>
|
|
613
629
|
|
|
630
|
+
<div class="card" id="c-recovery"><span id="anchor-recovery"></span>
|
|
631
|
+
<span class="eyebrow">Recovery key</span>
|
|
632
|
+
<h2 id="e-head">Loading…</h2>
|
|
633
|
+
<p id="e-text"></p>
|
|
634
|
+
</div>
|
|
635
|
+
|
|
614
636
|
<div class="card" id="anchor-storage">
|
|
615
637
|
<span class="eyebrow">Storage</span>
|
|
616
638
|
<div id="a-store-free" style="display:none">
|
|
@@ -662,7 +684,9 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
662
684
|
<span class="eyebrow">Machines</span>
|
|
663
685
|
<h2>Connected machines</h2>
|
|
664
686
|
<p>Each connected machine signs its own uploads. Revoking one stops it writing here; the archives it already stored are untouched.</p>
|
|
687
|
+
<div id="pair-top"></div>
|
|
665
688
|
<ul class="lines" id="d-list"></ul>
|
|
689
|
+
<div id="d-retired"></div>
|
|
666
690
|
<p class="empty" id="d-msg">Loading…</p>
|
|
667
691
|
|
|
668
692
|
<!-- One pairing block exists on the page. It leads the panel while no
|
|
@@ -700,44 +724,36 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
700
724
|
<p class="msg" id="s-msg">Loading…</p>
|
|
701
725
|
<div id="s-wrap" style="display:none;overflow-x:auto">
|
|
702
726
|
<table>
|
|
703
|
-
<thead><tr><th>Archive</th><th>
|
|
727
|
+
<thead><tr><th>Archive</th><th>Size</th><th>State</th><th>Added</th><th></th></tr></thead>
|
|
704
728
|
<tbody id="s-body"></tbody>
|
|
705
729
|
</table>
|
|
706
730
|
<p class="msg" id="s-foot"></p>
|
|
707
731
|
</div>
|
|
708
732
|
</div>
|
|
709
733
|
|
|
710
|
-
|
|
711
|
-
<span class="eyebrow">Plan</span>
|
|
712
|
-
<div id="a-plan-free" style="display:none">
|
|
713
|
-
<h2>Free — your bucket, your bill</h2>
|
|
714
|
-
<p>Unlimited archives with the same encryption. You supply the storage credentials and pay your provider directly.</p>
|
|
715
|
-
<p class="empty">Want us to run storage instead? Pricing is in Your package, below. Write to <span id="a-sales">hello@spala.ai</span> from this address and we will switch it on.</p>
|
|
716
|
-
</div>
|
|
717
|
-
<div id="a-plan-paid" style="display:none">
|
|
718
|
-
<h2>Managed storage</h2>
|
|
719
|
-
<p>We run the bucket and meter what you store. We still hold no key that opens any of it — the only difference from your own bucket is who pays the storage bill and who can delete it.</p>
|
|
720
|
-
<div class="kv" style="margin-top:1rem">
|
|
721
|
-
<div><span class="k">Subscription</span><b id="a-sub-state">—</b></div>
|
|
722
|
-
<div><span class="k">Renews</span><b id="a-sub-end">—</b></div>
|
|
723
|
-
<div><span class="k">Billed through</span><b id="a-sub-prov">—</b></div>
|
|
724
|
-
</div>
|
|
725
|
-
<p class="empty" id="a-sub-note"></p>
|
|
726
|
-
</div>
|
|
727
|
-
</div>
|
|
734
|
+
|
|
728
735
|
|
|
729
736
|
<div class="card" id="a-pack-card">
|
|
737
|
+
<span id="anchor-plan"></span>
|
|
730
738
|
<span class="eyebrow">Your package</span>
|
|
731
739
|
<h2 id="a-pack-current">—</h2>
|
|
732
740
|
<p class="msg" id="a-pack-context" style="margin:0 0 .8rem"></p>
|
|
733
741
|
<div class="packrow" id="a-packs"></div>
|
|
742
|
+
<p class="empty" id="a-plan-free-note"></p>
|
|
734
743
|
<p class="empty" id="a-pack-alt">Not storing with us? Your own bucket is free with the same encryption, and Pro Lite keeps key escrow and machine sign-in with no metered storage.</p>
|
|
744
|
+
<div class="kv" id="a-sub-facts" style="margin-top:1rem">
|
|
745
|
+
<div><span class="k">Subscription</span><b id="a-sub-state">—</b></div>
|
|
746
|
+
<div><span class="k">Renews</span><b id="a-sub-end">—</b></div>
|
|
747
|
+
<div><span class="k">Billed through</span><b id="a-sub-prov">—</b></div>
|
|
748
|
+
</div>
|
|
749
|
+
<p class="empty" id="a-sub-note"></p>
|
|
735
750
|
<p class="why" id="a-pack-note">Changing your package is handled by hand today. Email hello@spala.ai from this address and we will move you within one business day — your archives keep uploading throughout.</p>
|
|
736
751
|
</div>
|
|
737
752
|
|
|
738
753
|
<div class="card">
|
|
739
|
-
<span class="eyebrow">What
|
|
754
|
+
<span class="eyebrow">What gets uploaded</span>
|
|
740
755
|
<h2>Your upload rules</h2>
|
|
756
|
+
<p class="why" style="margin:.1rem 0 .8rem">Saved to your account — every machine you connect follows these, the same way routing rules do.</p>
|
|
741
757
|
<p>Everything you do not choose here stays local and is never uploaded.</p>
|
|
742
758
|
<p class="empty" id="r-note" style="margin-bottom:.9rem">Enforced on your machine, before a session is read or sealed. Anything held back is never uploaded, so this is a rule about what leaves — not a filter we apply after it arrives.</p>
|
|
743
759
|
<div class="rules">
|
|
@@ -764,11 +780,7 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
764
780
|
</div>
|
|
765
781
|
</div>
|
|
766
782
|
|
|
767
|
-
|
|
768
|
-
<span class="eyebrow">Recovery key</span>
|
|
769
|
-
<h2 id="e-head">Loading…</h2>
|
|
770
|
-
<p id="e-text"></p>
|
|
771
|
-
</div>
|
|
783
|
+
|
|
772
784
|
|
|
773
785
|
<div class="card">
|
|
774
786
|
<span class="eyebrow">Activity</span>
|
|
@@ -1051,7 +1063,7 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1051
1063
|
$("a-name").value = user.label || "";
|
|
1052
1064
|
$("a-plan").textContent = paid ? "Managed storage" : "Free";
|
|
1053
1065
|
$("a-plan").className = "chip" + (paid ? " pro" : "");
|
|
1054
|
-
$("a-plan").onclick = () => goTo($("
|
|
1066
|
+
$("a-plan").onclick = () => goTo($("anchor-plan"));
|
|
1055
1067
|
$("a-who").textContent = [
|
|
1056
1068
|
user.email,
|
|
1057
1069
|
"joined " + day(user.created_at),
|
|
@@ -1069,8 +1081,13 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1069
1081
|
lastPaid = paid;
|
|
1070
1082
|
applyStorageView();
|
|
1071
1083
|
on("anchor-archives", paid); on("nav-archives", paid);
|
|
1072
|
-
|
|
1073
|
-
|
|
1084
|
+
// One card carries billing now. A free account is told what managed
|
|
1085
|
+
// storage would give it; a paying one is told the state of what it bought.
|
|
1086
|
+
$("a-plan-free-note").textContent = paid
|
|
1087
|
+
? ""
|
|
1088
|
+
: "You are on your own storage: unlimited archives, same encryption, your provider's bill. Managed storage means we run the bucket and meter what you store — we still hold no key that opens any of it.";
|
|
1089
|
+
on("a-sub-facts", paid);
|
|
1090
|
+
on("a-plan-free-note", !paid);
|
|
1074
1091
|
|
|
1075
1092
|
if (paid) {
|
|
1076
1093
|
const quota = Number(user.quota_bytes) || 0, used = Number(user.used_bytes) || 0;
|
|
@@ -1117,7 +1134,37 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1117
1134
|
return deviceCount;
|
|
1118
1135
|
}
|
|
1119
1136
|
$("d-msg").textContent = "";
|
|
1120
|
-
|
|
1137
|
+
// Revocation never deletes a machine — the row stays as the record that
|
|
1138
|
+
// it once had access. On an account that has re-paired a few times that
|
|
1139
|
+
// buries the two machines actually running under a wall of history: 29
|
|
1140
|
+
// rows, 1666 px, nearly two screens, all looking alike. Active machines
|
|
1141
|
+
// are the answer to "what can write to my account right now"; the rest
|
|
1142
|
+
// fold away, counted and reachable, deleted by nobody.
|
|
1143
|
+
const active = devices.filter((d) => d.state !== "revoked");
|
|
1144
|
+
const retired = devices.filter((d) => d.state === "revoked");
|
|
1145
|
+
const foldHost = $("d-retired");
|
|
1146
|
+
foldHost.replaceChildren();
|
|
1147
|
+
if (retired.length) {
|
|
1148
|
+
const fold = document.createElement("details");
|
|
1149
|
+
const summary = document.createElement("summary");
|
|
1150
|
+
summary.textContent = `${retired.length} revoked machine${retired.length === 1 ? "" : "s"} — kept as the record, none can write`;
|
|
1151
|
+
const inner = document.createElement("ul");
|
|
1152
|
+
inner.className = "lines";
|
|
1153
|
+
for (const d of retired) {
|
|
1154
|
+
const li = document.createElement("li");
|
|
1155
|
+
const name = document.createElement("span");
|
|
1156
|
+
name.className = "grow";
|
|
1157
|
+
name.textContent = d.label || "Unnamed machine";
|
|
1158
|
+
const when = document.createElement("span");
|
|
1159
|
+
when.className = "when";
|
|
1160
|
+
when.textContent = "revoked " + ago(d.revoked_at);
|
|
1161
|
+
li.append(name, when);
|
|
1162
|
+
inner.append(li);
|
|
1163
|
+
}
|
|
1164
|
+
fold.append(summary, inner);
|
|
1165
|
+
foldHost.append(fold);
|
|
1166
|
+
}
|
|
1167
|
+
for (const d of active) {
|
|
1121
1168
|
const li = document.createElement("li");
|
|
1122
1169
|
const name = document.createElement("span");
|
|
1123
1170
|
name.className = "grow";
|
|
@@ -1138,7 +1185,13 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1138
1185
|
// Two-step, like archive deletion: revoking locks a machine out of
|
|
1139
1186
|
// its own backups, so a stray click must not do it.
|
|
1140
1187
|
if (btn.dataset.armed !== "yes") {
|
|
1141
|
-
btn.dataset.armed = "yes";
|
|
1188
|
+
btn.dataset.armed = "yes";
|
|
1189
|
+
// The local dashboard escalates to "Destroy forever?" when it can
|
|
1190
|
+
// prove a copy is the last one. This page cannot see the customer's
|
|
1191
|
+
// disk, so it must not claim either way — it names the stake and
|
|
1192
|
+
// lets them decide.
|
|
1193
|
+
btn.textContent = "Delete forever?";
|
|
1194
|
+
btn.title = "If your machine already reclaimed the source, this is the only copy";
|
|
1142
1195
|
setTimeout(() => {
|
|
1143
1196
|
if (btn.dataset.armed === "yes") { btn.dataset.armed = "no"; btn.textContent = "Revoke"; }
|
|
1144
1197
|
}, 4000);
|
|
@@ -1489,7 +1542,7 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1489
1542
|
: paidPlan ? "Sealkeep cloud" : "the bucket each machine is configured with");
|
|
1490
1543
|
const nothingYet = devices === 0 && Number(user.used_bytes) === 0;
|
|
1491
1544
|
on("a-firstrun", nothingYet);
|
|
1492
|
-
$(nothingYet ? "pair-home-first" : "pair-
|
|
1545
|
+
$(nothingYet ? "pair-home-first" : "pair-top").append($("pair"));
|
|
1493
1546
|
$("pair-side").textContent = nothingYet ? "Your pairing code" : "Pairing code";
|
|
1494
1547
|
if (devices === 0)
|
|
1495
1548
|
$("d-msg").textContent = nothingYet
|
|
@@ -1742,8 +1795,16 @@ sealkeep open 08424eb6… ./session.jsonl \
|
|
|
1742
1795
|
const host = $("a-packs");
|
|
1743
1796
|
while (host.firstChild) host.removeChild(host.firstChild);
|
|
1744
1797
|
for (const pack of PACKAGES.filter((p) => p.managed)) {
|
|
1745
|
-
const chip = document.createElement("span");
|
|
1798
|
+
const chip = document.createElement(pack.id === current ? "span" : "a");
|
|
1746
1799
|
chip.className = "packchip" + (pack.id === current ? " now" : "");
|
|
1800
|
+
if (pack.id !== current) {
|
|
1801
|
+
// It looked like a picker and did nothing when clicked. Changing a
|
|
1802
|
+
// package is handled by a person today, so the chip starts that
|
|
1803
|
+
// message rather than pretending to be a checkout.
|
|
1804
|
+
chip.href = "mailto:hello@spala.ai?subject=" + encodeURIComponent(`Move me to ${pack.name}`)
|
|
1805
|
+
+ "&body=" + encodeURIComponent(`Please move my account to ${pack.name}${pack.managed ? ` ($${pack.monthly} a month)` : ""}.`);
|
|
1806
|
+
chip.title = `Ask us to move you to ${pack.name}`;
|
|
1807
|
+
}
|
|
1747
1808
|
const name = document.createElement("b"); name.textContent = pack.name;
|
|
1748
1809
|
const price = document.createElement("span"); price.textContent = `$${pack.monthly}/mo`;
|
|
1749
1810
|
chip.append(name, price);
|
package/dist/src/doctor.js
CHANGED
|
@@ -169,6 +169,44 @@ export async function runDoctor(dataDir, env = process.env) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
catch { /* coverage is advisory; a broken sidecar must not fail doctor */ }
|
|
172
|
+
// A routing rule can pin a project to a destination, and the account panel
|
|
173
|
+
// cannot check the name: project names never reach the cloud. So a typo is
|
|
174
|
+
// saved happily and silently routes nothing, forever. This machine knows the
|
|
175
|
+
// real names, so it is the one that can say.
|
|
176
|
+
try {
|
|
177
|
+
const { resolveTargets } = await import("./storage-targets.js");
|
|
178
|
+
const targets = await resolveTargets(dataDir);
|
|
179
|
+
const pinned = [...new Set(targets.flatMap((target) => target.projects ?? []))];
|
|
180
|
+
if (pinned.length > 0) {
|
|
181
|
+
const { listArchives } = await import("./vault.js");
|
|
182
|
+
const { claudeProjectFromPath, codexProjectFromRollout } = await import("./adapters.js");
|
|
183
|
+
const records = await listArchives(dataDir).catch(() => []);
|
|
184
|
+
const known = new Set();
|
|
185
|
+
for (const record of records) {
|
|
186
|
+
try {
|
|
187
|
+
const name = record.source.agent === "claude"
|
|
188
|
+
? claudeProjectFromPath(record.source.path).project
|
|
189
|
+
: record.source.agent === "codex"
|
|
190
|
+
? (await codexProjectFromRollout(record.source.path, record.createdAt)).project
|
|
191
|
+
: null;
|
|
192
|
+
if (name)
|
|
193
|
+
known.add(name);
|
|
194
|
+
}
|
|
195
|
+
catch { /* a project we cannot read is not evidence of a bad pin */ }
|
|
196
|
+
}
|
|
197
|
+
const unmatched = pinned.filter((name) => !known.has(name));
|
|
198
|
+
if (unmatched.length === 0) {
|
|
199
|
+
checks.push({ name: "routing-pins", status: "pass", detail: `${pinned.length} pinned project${pinned.length === 1 ? "" : "s"} match sessions on this machine.` });
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
checks.push({
|
|
203
|
+
name: "routing-pins", status: "warn",
|
|
204
|
+
detail: `${unmatched.length === pinned.length ? "No" : unmatched.length} pinned project${unmatched.length === 1 ? "" : "s"} match anything sealed here: ${unmatched.slice(0, 3).join(", ")}. Those rules route nothing. Run \`sealkeep projects\` for the names this vault actually knows${known.size ? `, such as ${[...known].slice(0, 2).join(", ")}` : ""}.`
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch { /* routing rules are advisory here; never fail doctor over them */ }
|
|
172
210
|
// Where the daemon actually looks: an explicit env var, or the phrase the
|
|
173
211
|
// wizard put in this machine's keystore. This used to test only the env var,
|
|
174
212
|
// so a machine set up correctly through the window — phrase in the keystore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sealkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Sealkeep by SPALA AI \u2014 your AI coding-agent history, sealed, searchable, and shared across your machines.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"build:site": "node tools/build-site.mjs",
|
|
42
42
|
"bundle:site": "node tools/build-site.mjs && cd dist/site && zip -q -X ../site.zip index.html && cd - >/dev/null && echo \"dist/site.zip ready ($(wc -c < dist/site.zip) bytes)\"",
|
|
43
43
|
"acceptance": "node scripts/acceptance.mjs",
|
|
44
|
-
"acceptance:managed": "node scripts/acceptance.mjs --managed"
|
|
44
|
+
"acceptance:managed": "node scripts/acceptance.mjs --managed",
|
|
45
|
+
"tokens": "node scripts/sync-tokens.mjs"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/node": "^22.10.2",
|
package/web/setup.html
CHANGED
|
@@ -267,12 +267,12 @@
|
|
|
267
267
|
<p>It unlocks with your password on this machine. If you ever lose the password, the phrase below is the only other way in — which is a good reason to spend a minute on it now.</p>
|
|
268
268
|
</div>
|
|
269
269
|
<details class="panel pad explain" id="phrase-fold">
|
|
270
|
-
<summary>
|
|
270
|
+
<summary>Show the 24 words that survive this machine</summary>
|
|
271
271
|
<p id="fold-note"></p>
|
|
272
272
|
<ol class="phrase" id="fold-phrase-words"></ol>
|
|
273
273
|
</details>
|
|
274
274
|
<div class="wizfoot">
|
|
275
|
-
<p class="note">
|
|
275
|
+
<p class="note warn"><b>Your password opens this vault on this machine only.</b> It is kept in this machine's keystore — it does not travel. If this machine is lost or wiped, the 24 words above are the only thing that opens the archives, and nobody, including us, can recreate them. Take them now, or accept that this vault dies with this machine.</p>
|
|
276
276
|
<div class="actions"><button type="button" class="action primary" id="finish-password">Finish setup</button></div>
|
|
277
277
|
</div>
|
|
278
278
|
</div>
|
package/web/style.css
CHANGED
|
@@ -10,12 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
:root {
|
|
13
|
-
/*
|
|
14
|
-
of truth (see design-system/README.md). The older dashboard names below are
|
|
15
|
-
kept as aliases so existing rules resolve to the system's colours without a
|
|
16
|
-
full rename. The page is achromatic: the ONE saturated block is ciphertext
|
|
17
|
-
(--seal); --live marks what is interactive or currently true; --warn is
|
|
18
|
-
rationed to where inaction loses data. */
|
|
13
|
+
/* @tokens:start — generated from design-system/tokens.css, do not edit here */
|
|
19
14
|
--paper: #f4f4f3;
|
|
20
15
|
--card: #fbfbfa;
|
|
21
16
|
--ink: #111316;
|
|
@@ -26,14 +21,17 @@
|
|
|
26
21
|
--live: #2a3fb0;
|
|
27
22
|
--warn: #8c3220;
|
|
28
23
|
--warn-bg: #f4e6e2;
|
|
29
|
-
|
|
24
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, "Helvetica Neue", sans-serif;
|
|
30
25
|
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, "DejaVu Sans Mono", Consolas, monospace;
|
|
31
|
-
/*
|
|
32
|
-
any rule still asking for a second face complies instead of breaking the
|
|
33
|
-
rule the whole system is built on. */
|
|
34
|
-
--sans: var(--mono);
|
|
26
|
+
/* @tokens:end */
|
|
35
27
|
|
|
36
|
-
/*
|
|
28
|
+
/* Surface-local aliases: older dashboard rules ask for these names, and they
|
|
29
|
+
resolve to the system's colours rather than a second palette. Kept outside
|
|
30
|
+
the generated block on purpose — the token file describes the design
|
|
31
|
+
system, this describes one surface's history. */
|
|
32
|
+
/* Layout constants for this surface, not part of the shared palette. */
|
|
33
|
+
--rail: 264px;
|
|
34
|
+
--gap: 20px;
|
|
37
35
|
--panel: var(--card);
|
|
38
36
|
--rule-soft: #e6e7e9;
|
|
39
37
|
--text: var(--ink);
|
|
@@ -43,17 +41,8 @@
|
|
|
43
41
|
--verified: var(--live);
|
|
44
42
|
--held: var(--soft);
|
|
45
43
|
--alert: var(--warn);
|
|
46
|
-
/* Were dark-navy fills for the old rail; now the solid-ink fill the system
|
|
47
|
-
uses for a filled control. Lazy substitution keeps them theme-correct. */
|
|
48
44
|
--ink-2: var(--ink);
|
|
49
45
|
--ink-3: var(--soft);
|
|
50
|
-
/* Rail is now part of the light page, not a dark slab. These name faint
|
|
51
|
-
surfaces used for nav hover/active and glyphs. */
|
|
52
|
-
--rail-active: #ecedf0;
|
|
53
|
-
--rail-glyph: #9aa0a8;
|
|
54
|
-
|
|
55
|
-
--rail: 264px;
|
|
56
|
-
--gap: 20px;
|
|
57
46
|
}
|
|
58
47
|
|
|
59
48
|
/* ── Dark ─────────────────────────────────────────────── */
|
|
@@ -176,7 +165,9 @@ nav button .count { margin-left: auto; font-size: 11.5px; color: var(--soft); }
|
|
|
176
165
|
margin-top: auto;
|
|
177
166
|
border-top: 1px solid var(--rule);
|
|
178
167
|
padding: 18px 12px 0;
|
|
179
|
-
|
|
168
|
+
/* Prose, so it reads as prose. Mono is reserved for what a machine said —
|
|
169
|
+
a size, a path, a state, a command — and this is us explaining the page. */
|
|
170
|
+
font-family: var(--sans);
|
|
180
171
|
font-size: 12px;
|
|
181
172
|
color: var(--soft);
|
|
182
173
|
line-height: 1.65;
|
|
@@ -674,6 +665,16 @@ nav.steps .step.locked.current:hover { background: var(--ink-2); color: #fff; }
|
|
|
674
665
|
.callout h3 { margin: 0; }
|
|
675
666
|
.callout h3 + p { margin-top: -6px; }
|
|
676
667
|
.callout.warn { border-left: 3px solid var(--alert); }
|
|
668
|
+
/* A note that carries a consequence reads like one. Used where setup asks
|
|
669
|
+
whether to keep the 24 words: the copy there used to call skipping them
|
|
670
|
+
"fine", which is true until the machine is lost and then permanently false. */
|
|
671
|
+
.note.warn {
|
|
672
|
+
border-left: 3px solid var(--alert);
|
|
673
|
+
padding: 10px 12px;
|
|
674
|
+
background: color-mix(in srgb, var(--alert) 7%, transparent);
|
|
675
|
+
color: var(--ink);
|
|
676
|
+
}
|
|
677
|
+
.note.warn b { color: var(--alert); }
|
|
677
678
|
.callout.warn p { color: var(--text); }
|
|
678
679
|
ul.plain { margin: 0; padding-left: 19px; display: grid; gap: 8px; color: var(--text-2); max-width: 72ch; }
|
|
679
680
|
ul.plain b { color: var(--text); }
|