aegis-desktop 0.8.12 → 0.8.13
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/lib/auto.js +423 -0
- package/lib/avatar/packs/core-wardrobe.pack.json +1 -1
- package/lib/avatar/packs/local-voice-core.pack.json +1 -1
- package/lib/avatar/packs/seasonal-winter.pack.json +1 -1
- package/lib/avatar/profile.js +10 -1
- package/lib/fingerprint.js +327 -0
- package/lib/local/autonomous.js +1 -0
- package/lib/local/prompt.js +6 -0
- package/lib/settings.js +154 -1
- package/main.js +316 -17
- package/package.json +2 -2
- package/preload.js +84 -4
- package/renderer/app.js +208 -4
- package/renderer/fingerprint.css +143 -0
- package/renderer/fingerprint.js +583 -0
- package/renderer/index.html +163 -1
- package/vendor/aegis.js +180 -18
- package/vendor/attest.js +439 -0
- package/vendor/authorship-key.js +366 -0
- package/vendor/device-fingerprint.js +85 -3
- package/vendor/fingerprint.js +1061 -0
- package/vendor/presence-constants.js +104 -0
- package/vendor/presence.js +435 -0
- package/vendor/record-vault.js +834 -0
- package/vendor/touch.js +455 -0
package/renderer/app.js
CHANGED
|
@@ -42,6 +42,12 @@ const quickLauncher = window.quickLauncher;
|
|
|
42
42
|
// has no `queue` key, and every function below no-ops on it rather than
|
|
43
43
|
// crashing boot the way a chat flow without window.aegis would.
|
|
44
44
|
const queueApi = window.queue;
|
|
45
|
+
// The two opt-in automations (main.js registerAutoIpc -> lib/auto.js). Guarded
|
|
46
|
+
// everywhere it is used, for the same reason queueApi is: a preload predating
|
|
47
|
+
// this surface has no `window.auto` at all, and the toggles must degrade to
|
|
48
|
+
// inert markup rather than throwing at boot and taking every later binding with
|
|
49
|
+
// them.
|
|
50
|
+
const autoApi = window.auto;
|
|
45
51
|
|
|
46
52
|
if (!aegis || !models) {
|
|
47
53
|
document.body.textContent =
|
|
@@ -126,6 +132,13 @@ const ELEMENT_IDS = {
|
|
|
126
132
|
memoryCount: 'memory-count',
|
|
127
133
|
memoryOverlayEntry: 'memory-overlay-entry',
|
|
128
134
|
memoryOverlaySave: 'memory-overlay-save',
|
|
135
|
+
// φ(α). The panel's own nodes are read by desktop/renderer/fingerprint.js,
|
|
136
|
+
// which declares them itself; app.js only needs the sidebar state line it
|
|
137
|
+
// writes when mount() throws. Listed here all the same so the "every id
|
|
138
|
+
// app.js asks for exists in index.html" check covers them.
|
|
139
|
+
fpCardStatus: 'fp-card-status',
|
|
140
|
+
fpOpen: 'fp-open',
|
|
141
|
+
fpOverlay: 'fp-overlay',
|
|
129
142
|
messages: 'messages',
|
|
130
143
|
composer: 'composer',
|
|
131
144
|
prompt: 'prompt',
|
|
@@ -142,6 +155,10 @@ const ELEMENT_IDS = {
|
|
|
142
155
|
queueHint: 'queue-hint',
|
|
143
156
|
queueFanout: 'queue-fanout',
|
|
144
157
|
queueFanoutLabel: 'queue-fanout-label',
|
|
158
|
+
queueAuto: 'queue-auto',
|
|
159
|
+
queueAutoLabel: 'queue-auto-label',
|
|
160
|
+
queueAutoInterval: 'queue-auto-interval',
|
|
161
|
+
queueAutoHint: 'queue-auto-hint',
|
|
145
162
|
queueBoundary: 'queue-boundary',
|
|
146
163
|
queueUpgrade: 'queue-upgrade',
|
|
147
164
|
};
|
|
@@ -1543,6 +1560,119 @@ async function queueTaskAction(name, id) {
|
|
|
1543
1560
|
}
|
|
1544
1561
|
}
|
|
1545
1562
|
|
|
1563
|
+
/**
|
|
1564
|
+
* The auto-drain control on the queue card (main.js registerAutoIpc ->
|
|
1565
|
+
* desktop/lib/auto.js -> settings.js `__autoDrain`).
|
|
1566
|
+
*
|
|
1567
|
+
* This is the ONE thing on this card that can start a drain with no click, and
|
|
1568
|
+
* it exists because the user asked for it explicitly. Everything the queue
|
|
1569
|
+
* section above promises is still true of a default install: the switch is
|
|
1570
|
+
* off, main.js's timer is not even created, and nothing drains at boot or on
|
|
1571
|
+
* idle. What changed is that "off" is now a persisted default rather than an
|
|
1572
|
+
* absolute — so the honesty this file owes is a truthful paint of that switch,
|
|
1573
|
+
* including when the timer is armed but skipping (another host holds the lock,
|
|
1574
|
+
* the queue is empty), which is the state that otherwise reads as "the feature
|
|
1575
|
+
* is broken".
|
|
1576
|
+
*/
|
|
1577
|
+
function renderAutoDrain(state) {
|
|
1578
|
+
if (!els.queueAuto || !state || typeof state !== 'object') return;
|
|
1579
|
+
const enabled = state.enabled === true;
|
|
1580
|
+
els.queueAuto.checked = enabled;
|
|
1581
|
+
if (els.queueAutoInterval) {
|
|
1582
|
+
// A pick is only meaningful while the switch is on; disabling it keeps the
|
|
1583
|
+
// select from reading as an armed cadence next to an off switch.
|
|
1584
|
+
els.queueAutoInterval.disabled = !enabled;
|
|
1585
|
+
const ms = Number(state.intervalMs);
|
|
1586
|
+
if (Number.isFinite(ms)) els.queueAutoInterval.value = String(ms);
|
|
1587
|
+
}
|
|
1588
|
+
if (els.queueAutoLabel) {
|
|
1589
|
+
els.queueAutoLabel.textContent = enabled
|
|
1590
|
+
? `drain automatically · ${formatAutoInterval(state.intervalMs)}`
|
|
1591
|
+
: 'drain automatically';
|
|
1592
|
+
}
|
|
1593
|
+
if (!els.queueAutoHint) return;
|
|
1594
|
+
if (!enabled) {
|
|
1595
|
+
els.queueAutoHint.textContent = 'off — Run one / Drain all are the only triggers';
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const bits = [];
|
|
1599
|
+
if (state.busy) bits.push('a drain is running');
|
|
1600
|
+
else if (state.armed) bits.push('armed');
|
|
1601
|
+
if (state.ticks) bits.push(`${state.ticks} tick${state.ticks === 1 ? '' : 's'}`);
|
|
1602
|
+
if (state.drains) bits.push(`${state.drains} drained`);
|
|
1603
|
+
const skip = state.lastSkip && state.lastSkip.reason;
|
|
1604
|
+
if (skip) bits.push(`last check skipped: ${autoSkipText(skip, state.lastSkip)}`);
|
|
1605
|
+
else if (!state.ticks) bits.push('first check one interval after arming');
|
|
1606
|
+
els.queueAutoHint.textContent = bits.join(' · ');
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/** The skip reasons lib/auto.js records, in the card's own words. An
|
|
1610
|
+
* unrecognised reason is shown verbatim rather than dropped: a new skip the
|
|
1611
|
+
* card does not know about is exactly the one worth reading. */
|
|
1612
|
+
function autoSkipText(reason, detail = {}) {
|
|
1613
|
+
if (reason === 'empty') return 'queue is empty';
|
|
1614
|
+
if (reason === 'busy') return 'the previous drain is still running';
|
|
1615
|
+
if (reason === 'locked') {
|
|
1616
|
+
const pid = detail.holder && detail.holder.pid;
|
|
1617
|
+
return pid ? `another drain holds the lock (pid ${pid})` : 'another drain holds the lock';
|
|
1618
|
+
}
|
|
1619
|
+
if (reason === 'disabled') return 'switched off';
|
|
1620
|
+
if (reason === 'no queue access') return 'this window has no queue access';
|
|
1621
|
+
return String(reason);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function formatAutoInterval(ms) {
|
|
1625
|
+
const n = Number(ms);
|
|
1626
|
+
if (!Number.isFinite(n) || n <= 0) return 'every interval';
|
|
1627
|
+
if (n < 60_000) return `every ${Math.round(n / 1000)}s`;
|
|
1628
|
+
const min = n / 60_000;
|
|
1629
|
+
return Number.isInteger(min) ? `every ${min} min` : `every ${min.toFixed(1)} min`;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/** Paint both automation states from main's answer (`auto.status()`). */
|
|
1633
|
+
function renderAutoStatus(payload) {
|
|
1634
|
+
if (!payload || typeof payload !== 'object') return;
|
|
1635
|
+
if (payload.drain) renderAutoDrain(payload.drain);
|
|
1636
|
+
if (typeof renderAutoMint === 'function' && payload.mint) renderAutoMint(payload.mint);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
/**
|
|
1640
|
+
* Load the two switches and paint them. A read, never an arm: main.js is what
|
|
1641
|
+
* armed the timer at boot, from the same persisted settings this paints.
|
|
1642
|
+
*/
|
|
1643
|
+
async function loadAutoState() {
|
|
1644
|
+
if (!autoApi || typeof autoApi.status !== 'function') return null;
|
|
1645
|
+
try {
|
|
1646
|
+
const state = await autoApi.status();
|
|
1647
|
+
renderAutoStatus(state);
|
|
1648
|
+
return state;
|
|
1649
|
+
} catch (err) {
|
|
1650
|
+
if (els.queueAutoHint) {
|
|
1651
|
+
els.queueAutoHint.textContent = `automation status unavailable: ${err && err.message ? err.message : err}`;
|
|
1652
|
+
}
|
|
1653
|
+
return null;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/** The drain toggle. Persists through main, then paints what main says it
|
|
1658
|
+
* persisted — never the optimistic checkbox state. */
|
|
1659
|
+
async function setAutoDrain(enabled) {
|
|
1660
|
+
if (!autoApi || typeof autoApi.setDrain !== 'function') return;
|
|
1661
|
+
const patch = { enabled: Boolean(enabled) };
|
|
1662
|
+
if (els.queueAutoInterval && els.queueAutoInterval.value) {
|
|
1663
|
+
patch.intervalMs = Number(els.queueAutoInterval.value);
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
const res = await autoApi.setDrain(patch);
|
|
1667
|
+
if (res && res.ok === false) {
|
|
1668
|
+
if (els.queueAutoHint) els.queueAutoHint.textContent = res.error || 'could not save that';
|
|
1669
|
+
}
|
|
1670
|
+
renderAutoStatus(res);
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
if (els.queueAutoHint) els.queueAutoHint.textContent = `could not save that: ${err && err.message ? err.message : err}`;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1546
1676
|
/** Live drain progress -> one status line. `delta`/`reasoning` frames are
|
|
1547
1677
|
* deliberately dropped: they are a worker's own output, and the place for a
|
|
1548
1678
|
* task's result is its row (from queue.list()), not the open transcript. */
|
|
@@ -1628,10 +1758,35 @@ const memoryView = {
|
|
|
1628
1758
|
loading: false,
|
|
1629
1759
|
};
|
|
1630
1760
|
|
|
1761
|
+
/** Is any sheet up?
|
|
1762
|
+
*
|
|
1763
|
+
* Both overlays count, because the one thing this predicate is used for is
|
|
1764
|
+
* Escape precedence: while a sheet is open, Escape must not reach past it to
|
|
1765
|
+
* cancel a turn the user may not be looking at (see the bindEscapeInterrupt
|
|
1766
|
+
* call). The memory inspector and the φ(α) panel are each opened over the same
|
|
1767
|
+
* sidebar, so "an overlay is open" has to mean either one — the wiring guard in
|
|
1768
|
+
* test/renderer-wiring.test.mjs pins this name against exactly that job.
|
|
1769
|
+
*
|
|
1770
|
+
* Callers that mean the MEMORY inspector specifically — which one is up decides
|
|
1771
|
+
* where a save reads from and what gets repainted — ask memoryOverlayOpen()
|
|
1772
|
+
* instead. Confusing the two is how a save would read a hidden textarea. */
|
|
1631
1773
|
function overlayOpen() {
|
|
1774
|
+
if (memoryOverlayOpen()) return true;
|
|
1775
|
+
return !!(window.aegisFingerprint && window.aegisFingerprint.isOpen());
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/** The memory inspector alone. Split out of overlayOpen() when the authorship
|
|
1779
|
+
* panel gained the same Escape treatment: the two questions were the same
|
|
1780
|
+
* question right up until there was a second sheet. */
|
|
1781
|
+
function memoryOverlayOpen() {
|
|
1632
1782
|
return !!els.memoryOverlay && !els.memoryOverlay.hidden;
|
|
1633
1783
|
}
|
|
1634
1784
|
|
|
1785
|
+
/** The φ(α) panel handle, set by init() when desktop/renderer/fingerprint.js is
|
|
1786
|
+
* loaded. Kept here rather than inside fingerprint.js so the Escape policy
|
|
1787
|
+
* above can ask it whether it is open without reaching into its internals. */
|
|
1788
|
+
let fpPanel = null;
|
|
1789
|
+
|
|
1635
1790
|
/** "3m ago" / "2d ago" from epoch ms. Empty string when unknown. */
|
|
1636
1791
|
function relTime(ms) {
|
|
1637
1792
|
if (!Number.isFinite(ms)) return '';
|
|
@@ -1927,7 +2082,7 @@ function renderMemoryChips() {
|
|
|
1927
2082
|
|
|
1928
2083
|
/** Repaint the inspector from `memoryView`. Pure — makes no network calls. */
|
|
1929
2084
|
function renderMemoryOverlay() {
|
|
1930
|
-
if (!
|
|
2085
|
+
if (!memoryOverlayOpen()) return;
|
|
1931
2086
|
const total = memoryView.entries.length;
|
|
1932
2087
|
els.memoryList.innerHTML = '';
|
|
1933
2088
|
renderMemoryChips();
|
|
@@ -2040,7 +2195,7 @@ function closeMemoryOverlay() {
|
|
|
2040
2195
|
/** Textarea the save button should read from — the inspector's when it is up,
|
|
2041
2196
|
* otherwise the sidebar's. Keeps both save paths on one code path. */
|
|
2042
2197
|
function activeMemoryEntry() {
|
|
2043
|
-
return
|
|
2198
|
+
return memoryOverlayOpen() && els.memoryOverlayEntry ? els.memoryOverlayEntry : els.memoryEntry;
|
|
2044
2199
|
}
|
|
2045
2200
|
|
|
2046
2201
|
async function importMemory() {
|
|
@@ -2123,7 +2278,7 @@ async function saveMemory() {
|
|
|
2123
2278
|
async function refreshMemoryViews() {
|
|
2124
2279
|
const q = els.memoryQuery ? els.memoryQuery.value.trim() : '';
|
|
2125
2280
|
await searchMemory(q);
|
|
2126
|
-
if (
|
|
2281
|
+
if (memoryOverlayOpen()) await loadMemoryOverlay(memoryView.query);
|
|
2127
2282
|
}
|
|
2128
2283
|
|
|
2129
2284
|
// ------------------------------------------------------------ chat welcome
|
|
@@ -4288,6 +4443,27 @@ async function init() {
|
|
|
4288
4443
|
await loadQueueState();
|
|
4289
4444
|
}
|
|
4290
4445
|
|
|
4446
|
+
// ── the two automation switches ──────────────────────────────────────────
|
|
4447
|
+
// Guarded like the queue block above (a preload predating registerAutoIpc has
|
|
4448
|
+
// no window.auto). The `change` handlers are the ONLY things that arm a
|
|
4449
|
+
// timer: loadAutoState() below reads and paints, and main.js armed whatever
|
|
4450
|
+
// was already persisted before this window existed. Nothing here fires one on
|
|
4451
|
+
// its own.
|
|
4452
|
+
if (autoApi) {
|
|
4453
|
+
if (els.queueAuto) {
|
|
4454
|
+
els.queueAuto.addEventListener('change', () => setAutoDrain(els.queueAuto.checked));
|
|
4455
|
+
}
|
|
4456
|
+
if (els.queueAutoInterval) {
|
|
4457
|
+
// A cadence change while armed re-arms the timer in main.js (arm() sees a
|
|
4458
|
+
// new intervalMs); while off it only persists, which is what the user
|
|
4459
|
+
// picked the value for.
|
|
4460
|
+
els.queueAutoInterval.addEventListener('change', () => {
|
|
4461
|
+
if (els.queueAuto && els.queueAuto.checked) setAutoDrain(true);
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
await loadAutoState();
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4291
4467
|
// The transcript's scroll/paint/Escape policy. Both listeners are registered
|
|
4292
4468
|
// inside transcript-view.js so the behaviours they enforce are the ones
|
|
4293
4469
|
// test/renderer-dom.test.mjs drives: the passive `scroll` listener is the
|
|
@@ -4315,8 +4491,20 @@ async function init() {
|
|
|
4315
4491
|
doc: document,
|
|
4316
4492
|
// The memory overlay wins: while it is open, Escape closes it rather than
|
|
4317
4493
|
// reaching past it to cancel a turn the user may not be looking at.
|
|
4494
|
+
//
|
|
4495
|
+
// φ(α) is composed into overlayOpen() rather than given its own listener:
|
|
4496
|
+
// two keydown handlers racing to consume one Escape is how a panel ends up
|
|
4497
|
+
// closing and a turn getting cancelled by the same keypress. The escaping
|
|
4498
|
+
// order is the φ(α) panel first only because it is the outermost sheet when
|
|
4499
|
+
// both are somehow up.
|
|
4318
4500
|
isOverlayOpen: overlayOpen,
|
|
4319
|
-
onOverlayEscape:
|
|
4501
|
+
onOverlayEscape: () => {
|
|
4502
|
+
if (window.aegisFingerprint && window.aegisFingerprint.isOpen()) {
|
|
4503
|
+
window.aegisFingerprint.close();
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
closeMemoryOverlay();
|
|
4507
|
+
},
|
|
4320
4508
|
hasPendingTurn: () => !!pendingSessionId,
|
|
4321
4509
|
stopTurn: stopPendingTurn,
|
|
4322
4510
|
});
|
|
@@ -4373,6 +4561,22 @@ async function init() {
|
|
|
4373
4561
|
await refreshSyncStatus();
|
|
4374
4562
|
loadAccountInfo();
|
|
4375
4563
|
searchMemory('');
|
|
4564
|
+
|
|
4565
|
+
// φ(α). `?`-guarded like the update banner above: desktop/renderer/
|
|
4566
|
+
// fingerprint.js owns its own markup and its own listeners, so the sidebar
|
|
4567
|
+
// card and the panel are painted from one mount call and a missing bridge
|
|
4568
|
+
// (a host that predates the IPC handlers) leaves the card on "checking…"
|
|
4569
|
+
// instead of crashing boot. Not awaited: the panel's first status read is a
|
|
4570
|
+
// filesystem stat, and the welcome panel must not wait on it.
|
|
4571
|
+
if (window.aegisFingerprint) {
|
|
4572
|
+
try {
|
|
4573
|
+
fpPanel = window.aegisFingerprint.mount({ api: aegis });
|
|
4574
|
+
fpPanel.refresh().catch(() => {});
|
|
4575
|
+
} catch (e) {
|
|
4576
|
+
const card = els.fpCardStatus;
|
|
4577
|
+
if (card) card.textContent = `authorship panel unavailable: ${(e && e.message) || e}`;
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4376
4580
|
}
|
|
4377
4581
|
|
|
4378
4582
|
// Boot only after the DOM is parsed so every control this script queries
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* ---------------------------------------------------------- φ(α) panel
|
|
2
|
+
The panel reuses the memory inspector's sheet (.memory-overlay /
|
|
3
|
+
.memory-panel, style.css:483+) so the two overlays are the same object to the
|
|
4
|
+
eye — one translucent layer, one sheet, Escape closes both. Only the rows the
|
|
5
|
+
fingerprint panel adds are styled here, in their own file, because
|
|
6
|
+
style.css is not where this feature lives.
|
|
7
|
+
|
|
8
|
+
Loaded by index.html after style.css; `style-src 'self'` allows it. */
|
|
9
|
+
|
|
10
|
+
.fp-rows {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
gap: 2px 18px;
|
|
14
|
+
padding: 12px 16px 4px;
|
|
15
|
+
font-family: var(--font-mono);
|
|
16
|
+
font-size: 11px;
|
|
17
|
+
color: var(--text2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.fp-row {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: 6px;
|
|
23
|
+
min-width: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.fp-k {
|
|
27
|
+
color: var(--text3, var(--text2));
|
|
28
|
+
opacity: 0.75;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.fp-v {
|
|
32
|
+
color: var(--text);
|
|
33
|
+
overflow-wrap: anywhere;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* The 0644 refusal and any other "the fix is a command" message. Warning
|
|
37
|
+
colour, not error colour: the key works, it is just readable by others. */
|
|
38
|
+
.fp-warn {
|
|
39
|
+
margin: 6px 16px 0;
|
|
40
|
+
font-size: 11px;
|
|
41
|
+
font-family: var(--font-mono);
|
|
42
|
+
color: var(--warn, #e0a33a);
|
|
43
|
+
overflow-wrap: anywhere;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.fp-warn[hidden] {
|
|
47
|
+
display: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fp-controls {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 6px;
|
|
54
|
+
padding: 10px 16px 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.fp-line {
|
|
58
|
+
display: flex;
|
|
59
|
+
gap: 6px;
|
|
60
|
+
align-items: center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* The backup row sits outside .fp-controls, so it carries its own inset.
|
|
64
|
+
A class rather than a style attribute — the CSP is `style-src 'self'`. */
|
|
65
|
+
.fp-line-inset {
|
|
66
|
+
padding: 8px 16px 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.fp-line input[type='text'] {
|
|
70
|
+
flex: 1;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
background: var(--surface);
|
|
73
|
+
color: var(--text);
|
|
74
|
+
border: 1px solid var(--border);
|
|
75
|
+
border-radius: 6px;
|
|
76
|
+
padding: 6px 8px;
|
|
77
|
+
font: inherit;
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.fp-actions {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-wrap: wrap;
|
|
84
|
+
gap: 6px;
|
|
85
|
+
padding: 8px 16px 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.fp-vault-list {
|
|
89
|
+
list-style: none;
|
|
90
|
+
margin: 6px 0 0;
|
|
91
|
+
padding: 0 16px;
|
|
92
|
+
max-height: 150px;
|
|
93
|
+
overflow: auto;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.fp-vault-row {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
align-items: baseline;
|
|
100
|
+
padding: 4px 0;
|
|
101
|
+
border-bottom: 1px solid var(--border);
|
|
102
|
+
font-size: 11px;
|
|
103
|
+
font-family: var(--font-mono);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.fp-vault-name {
|
|
107
|
+
color: var(--text);
|
|
108
|
+
overflow-wrap: anywhere;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.fp-vault-detail {
|
|
112
|
+
color: var(--text2);
|
|
113
|
+
overflow-wrap: anywhere;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* The result line. `fp-bad` is a refusal, which is always a sentence naming
|
|
117
|
+
the reason — never a blank pane that reads like success. */
|
|
118
|
+
.fp-out {
|
|
119
|
+
margin: 10px 16px 14px;
|
|
120
|
+
padding: 8px 10px;
|
|
121
|
+
border: 1px solid var(--border);
|
|
122
|
+
border-radius: 6px;
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
font-family: var(--font-mono);
|
|
125
|
+
color: var(--text2);
|
|
126
|
+
overflow-wrap: anywhere;
|
|
127
|
+
min-height: 30px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.fp-out.fp-bad {
|
|
131
|
+
color: var(--danger, #e06c75);
|
|
132
|
+
border-color: color-mix(in srgb, var(--danger, #e06c75) 45%, var(--border));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Sidebar card: the state line under the heading, and the button that opens
|
|
136
|
+
the sheet. */
|
|
137
|
+
#fp-card-status {
|
|
138
|
+
font-family: var(--font-mono);
|
|
139
|
+
font-size: 11px;
|
|
140
|
+
color: var(--text2);
|
|
141
|
+
overflow-wrap: anywhere;
|
|
142
|
+
margin: 0 0 6px;
|
|
143
|
+
}
|