agentbox-app 0.1.11 → 0.1.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/README.md +24 -16
- package/bin/agentbox.mjs +18 -9
- package/briefs/message-rules.md +15 -4
- package/main/account-tooling.mjs +2 -1
- package/main/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +57 -20
- package/main/agent-updates.mjs +9 -20
- package/main/analytics.mjs +30 -0
- package/main/approvals.mjs +14 -5
- package/main/claude-bin.mjs +45 -310
- package/main/claude-input.mjs +9 -3
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +14 -2
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -248
- package/main/codex-launch-env.mjs +31 -0
- package/main/codex-session.mjs +38 -3
- package/main/codex-threads.mjs +41 -8
- package/main/codex-watch.mjs +67 -3
- package/main/config.mjs +40 -13
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/demo.mjs +17 -14
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +27 -27
- package/main/feedback.mjs +11 -6
- package/main/fresh-user.mjs +1 -1
- package/main/git-change.mjs +8 -2
- package/main/harnesses/claude-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +84 -0
- package/main/harnesses/codex.mjs +65 -0
- package/main/harnesses/common.mjs +78 -0
- package/main/harnesses/instructions-file.mjs +116 -0
- package/main/harnesses/opencode.mjs +234 -0
- package/main/harnesses.mjs +65 -0
- package/main/importable-threads.mjs +38 -0
- package/main/instruction-settings.mjs +47 -11
- package/main/ipc.mjs +175 -22
- package/main/local-app-frames.mjs +82 -0
- package/main/machine.mjs +1 -1
- package/main/main.mjs +120 -5
- package/main/memory-gate-history.mjs +51 -12
- package/main/memory-gate-server.mjs +67 -11
- package/main/memory-gate.mjs +49 -8
- package/main/memory-log.mjs +124 -0
- package/main/notify.mjs +48 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/refresh-agent-models.mjs +2 -1
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +8 -7
- package/main/runs-on.mjs +90 -0
- package/main/serve.mjs +124 -10
- package/main/settings.mjs +171 -11
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +25 -7
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +27 -1
- package/main/start-hold.mjs +73 -0
- package/main/store/home.mjs +37 -5
- package/main/store/project.mjs +15 -19
- package/main/store.mjs +10 -4
- package/main/supervisor.mjs +1030 -447
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +491 -82
- package/main/task-terminals.mjs +49 -8
- package/main/team/index.mjs +2 -2
- package/main/team/projects.mjs +1 -1
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- package/mcp/core/page.mjs +1 -1
- package/mcp/core/work.mjs +34 -3
- package/mcp/tools.mjs +2 -1
- package/package.json +10 -1
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
- package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
- package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
- package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
- package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
- package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
- package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
- package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
- package/renderer/dist/corner-tag.html +13 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +297 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/index.html +16 -3
- package/scripts/after-install.mjs +14 -0
- package/scripts/approval-server.sh +31 -7
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +3 -1
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +19 -0
- package/shared/claude-commands.generated.mjs +8 -6
- package/shared/claude-models.generated.mjs +10 -8
- package/shared/codex-modes.mjs +1 -1
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/demo-world.mjs +1 -1
- package/shared/engines.mjs +20 -5
- package/shared/fresh-user-home.mjs +12 -1
- package/shared/harness-definitions.mjs +103 -0
- package/shared/instruction-defaults.json +1 -1
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- package/shared/project-folder-check.mjs +3 -3
- package/shared/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +2 -0
- package/shared/rank.mjs +50 -0
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- package/shared/runs-on.d.mts +10 -0
- package/shared/runs-on.mjs +55 -0
- package/shared/shortcuts.mjs +11 -5
- package/shared/spawn-trouble.mjs +58 -7
- package/shared/terminal-input.d.mts +1 -0
- package/shared/terminal-input.mjs +30 -0
- package/shared/terminal-keys.d.mts +1 -0
- package/shared/terminal-keys.mjs +16 -0
- package/shared/terminal-size.d.mts +4 -0
- package/shared/terminal-size.mjs +29 -0
- package/shared/usage.d.mts +3 -0
- package/shared/usage.mjs +7 -0
- package/shared/where-it-runs-from.d.mts +43 -0
- package/shared/where-it-runs-from.mjs +155 -0
- package/shared/work-items.mjs +28 -2
- package/shared/work-lines.mjs +1 -1
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-B-2Az952.css +0 -32
- package/renderer/dist/assets/index-D6qncgai.js +0 -1168
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
// THE CORNER TAG'S WINDOWS (w-dafae58a23). shared/corner-tag.mjs decides what
|
|
2
|
+
// it says, when it shows and where it sits; this file is the live half: the
|
|
3
|
+
// tag's own small window, the list that opens beside it, the screens they sit
|
|
4
|
+
// on, and her choices about them.
|
|
5
|
+
//
|
|
6
|
+
// What the windows have to get right, each learned from the drawings, from
|
|
7
|
+
// real-Electron runs, from four reviews by Codex, and from real mouse events
|
|
8
|
+
// posted at the real windows (2026-10-08), which is where the last three of
|
|
9
|
+
// these were found:
|
|
10
|
+
//
|
|
11
|
+
// - A PRESS ON THE TAG MUST NOT BRING THE APP FORWARD. It did, on every
|
|
12
|
+
// floating-window setting tried: macOS makes the app active on a press, and
|
|
13
|
+
// a window that may not take focus hands it to the app's main window, which
|
|
14
|
+
// comes to the front. So the tag and its list MAY take focus. A press makes
|
|
15
|
+
// the tag the active window and the app's window stays exactly where it was.
|
|
16
|
+
// - EACH WINDOW IS EXACTLY ITS PILL OR ITS LIST. The first version padded the
|
|
17
|
+
// windows for a drawn shadow and let clicks fall through the padding, and
|
|
18
|
+
// switching that on and off under the pointer told the page the pointer had
|
|
19
|
+
// left, so a real first click fell straight through to the app beneath.
|
|
20
|
+
// With no margin there is nothing to fall through. The page draws the
|
|
21
|
+
// glass, its small corners and its edge. The window has no system shadow:
|
|
22
|
+
// with one, macOS also drew its own window outline (a black hairline and a
|
|
23
|
+
// light band) around the glass, a strong border no picture had shown.
|
|
24
|
+
// - A DRAG FOLLOWS THE REAL CURSOR. The page only says when the button went
|
|
25
|
+
// down and up; between the two, this process reads the cursor itself, so a
|
|
26
|
+
// drag never depends on the page being sent every move.
|
|
27
|
+
// - POINTING OPENS THE LIST, A CLICK OPENS THE APP (her ask, 2026-10-08: with
|
|
28
|
+
// 16 waiting she wants to go straight in, not click through the list). The
|
|
29
|
+
// pointer has to rest on the tag a moment, so passing over the corner opens
|
|
30
|
+
// nothing, and after a drag it has to leave and come back. The list is in
|
|
31
|
+
// its own window so the tag never moves. Off both for a moment and it shuts.
|
|
32
|
+
// - IT SHOWS OVER FULL-SCREEN APPS, IT HIDES FOR A WHILE AND COMES BACK, it is
|
|
33
|
+
// off for good only in Settings, it steps aside while she is in the app, and
|
|
34
|
+
// it goes when the app's window goes.
|
|
35
|
+
//
|
|
36
|
+
// What the tag counts is not decided here. Only the page knows what the inbox
|
|
37
|
+
// is, so it hands over what is ready and how many are working on
|
|
38
|
+
// `zero:corner-tag`; shared/corner-tag.mjs puts it newest first.
|
|
39
|
+
|
|
40
|
+
import { cardPlacement, hiddenUntil, keepCorner, readyNow, restingSpot, tagShows, HIDE_CHOICES } from '../shared/corner-tag.mjs';
|
|
41
|
+
|
|
42
|
+
// How long the pointer may be off both the tag and its list before the list
|
|
43
|
+
// shuts: long enough to cross the gap between them, short enough to feel like
|
|
44
|
+
// it follows the pointer.
|
|
45
|
+
export const CLOSE_AFTER_MS = 450;
|
|
46
|
+
// How long the pointer rests on the tag before the list opens: long enough
|
|
47
|
+
// that crossing the corner on the way to something else opens nothing.
|
|
48
|
+
export const OPEN_AFTER_MS = 220;
|
|
49
|
+
// Far enough that a click with a shaky hand is still a click.
|
|
50
|
+
export const DRAG_FROM_PX = 6;
|
|
51
|
+
// How often a drag reads the cursor: once a frame.
|
|
52
|
+
const FRAME_MS = 16;
|
|
53
|
+
// A press whose release never came is let go after this long.
|
|
54
|
+
const PRESS_GIVES_UP_MS = 60_000;
|
|
55
|
+
|
|
56
|
+
const HIDE_LABELS = { '5m': 'Hide for 5 minutes', '30m': 'Hide for 30 minutes', today: 'Hide for the rest of today' };
|
|
57
|
+
const CHANNELS = [
|
|
58
|
+
'zero:corner-tag', 'corner-tag:ready', 'corner-tag:size', 'corner-tag:hover', 'corner-tag:toggle',
|
|
59
|
+
'corner-tag:press', 'corner-tag:release', 'corner-tag:go', 'corner-tag:hide', 'corner-tag:settings', 'corner-tag:menu',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export function createCornerTag({
|
|
63
|
+
BrowserWindow, screen, ipcMain, Menu, powerMonitor, config, saveConfig, mainWindow, load, preload,
|
|
64
|
+
now = () => Date.now(), setTimer = setTimeout, clearTimer = clearTimeout, mac = process.platform === 'darwin',
|
|
65
|
+
}) {
|
|
66
|
+
const wins = { tag: null, card: null };
|
|
67
|
+
const loaded = { tag: false, card: false };
|
|
68
|
+
const hovered = { tag: false, card: false };
|
|
69
|
+
let ready = [];
|
|
70
|
+
let working = 0;
|
|
71
|
+
// The tag's rectangle on screen; its window is exactly this.
|
|
72
|
+
let tag = null;
|
|
73
|
+
let tagSize = { width: 96, height: 26 };
|
|
74
|
+
let measured = false;
|
|
75
|
+
let cardSize = null;
|
|
76
|
+
let open = false;
|
|
77
|
+
// A press on the tag: where the cursor and the tag were when it went down,
|
|
78
|
+
// and whether it has moved far enough to be a drag.
|
|
79
|
+
let press = null;
|
|
80
|
+
let wake = null;
|
|
81
|
+
let closing = null;
|
|
82
|
+
let watching = null;
|
|
83
|
+
let opening = null;
|
|
84
|
+
// After a drag the pointer is still on the tag; the list waits for it to
|
|
85
|
+
// leave and come back rather than opening under it.
|
|
86
|
+
let settling = false;
|
|
87
|
+
let gone = false;
|
|
88
|
+
|
|
89
|
+
const fresh = () => readyNow(ready, now());
|
|
90
|
+
const areas = () => screen.getAllDisplays().map((d) => d.workArea);
|
|
91
|
+
const areaOf = (r) => screen.getDisplayMatching({ x: Math.round(r.x), y: Math.round(r.y), width: Math.max(1, Math.round(r.width)), height: Math.max(1, Math.round(r.height)) }).workArea;
|
|
92
|
+
const inApp = () => {
|
|
93
|
+
try { return !!mainWindow && !mainWindow.isDestroyed() && mainWindow.isFocused(); } catch { return false; }
|
|
94
|
+
};
|
|
95
|
+
const alive = (part) => !!wins[part] && !wins[part].isDestroyed();
|
|
96
|
+
const rounded = (r) => ({ x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height) });
|
|
97
|
+
const send = (part, channel, payload) => { if (alive(part) && loaded[part]) wins[part].webContents.send(channel, payload); };
|
|
98
|
+
const sendState = () => {
|
|
99
|
+
const s = { ready: fresh(), working, now: now(), open };
|
|
100
|
+
send('tag', 'corner-tag:state', s);
|
|
101
|
+
send('card', 'corner-tag:state', s);
|
|
102
|
+
};
|
|
103
|
+
const shouldShow = () => tagShows({
|
|
104
|
+
on: config.cornerTag, hiddenUntil: config.cornerTagHiddenUntil ?? 0, now: now(), inApp: inApp(), ready: fresh().length, working,
|
|
105
|
+
});
|
|
106
|
+
// The list may only appear beside a tag that is on screen and should be.
|
|
107
|
+
// A click that was on its way while the tag hid must not open it alone.
|
|
108
|
+
const tagUp = () => !gone && alive('tag') && wins.tag.isVisible() && shouldShow();
|
|
109
|
+
|
|
110
|
+
const placeTag = () => {
|
|
111
|
+
const spot = restingSpot(tag ? { x: tag.x, y: tag.y } : config.cornerTagSpot ?? null, tagSize, areas());
|
|
112
|
+
tag = { ...spot, ...tagSize };
|
|
113
|
+
if (alive('tag')) wins.tag.setBounds(rounded(tag));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const placeCard = () => {
|
|
117
|
+
if (!tag || !cardSize || !alive('card')) return;
|
|
118
|
+
wins.card.setBounds(rounded(cardPlacement(tag, cardSize, areaOf(tag)).card));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const make = (part) => {
|
|
122
|
+
const win = new BrowserWindow({
|
|
123
|
+
show: false,
|
|
124
|
+
frame: false,
|
|
125
|
+
transparent: true,
|
|
126
|
+
backgroundColor: '#00000000',
|
|
127
|
+
// Clear and unrounded: the page draws the glass and its small corners.
|
|
128
|
+
// The system's frosted material would blur what is behind, but it comes
|
|
129
|
+
// only with the system's own rounder corners or square ones, and the
|
|
130
|
+
// approved tag has neither.
|
|
131
|
+
roundedCorners: false,
|
|
132
|
+
hasShadow: false,
|
|
133
|
+
resizable: false,
|
|
134
|
+
movable: false,
|
|
135
|
+
minimizable: false,
|
|
136
|
+
maximizable: false,
|
|
137
|
+
fullscreenable: false,
|
|
138
|
+
skipTaskbar: true,
|
|
139
|
+
// May take focus, so a press never hands it to the app's main window.
|
|
140
|
+
focusable: true,
|
|
141
|
+
// The first click on a window that is not active still counts.
|
|
142
|
+
acceptFirstMouse: true,
|
|
143
|
+
alwaysOnTop: true,
|
|
144
|
+
// A panel floats over full-screen apps without turning the whole app
|
|
145
|
+
// into a background app, which would take the dock icon away.
|
|
146
|
+
...(mac ? { type: 'panel' } : {}),
|
|
147
|
+
webPreferences: { preload, contextIsolation: true, nodeIntegration: false, sandbox: true },
|
|
148
|
+
});
|
|
149
|
+
win.setAlwaysOnTop(true, 'floating');
|
|
150
|
+
try { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true, skipTransformProcessType: true }); } catch {}
|
|
151
|
+
win.on('closed', () => { wins[part] = null; loaded[part] = false; });
|
|
152
|
+
// A reload starts the handshake again: the window goes, anything half
|
|
153
|
+
// done with it is let go, and until the page says it is ready nothing is
|
|
154
|
+
// sent to it and it is not shown. The first load is not a reload.
|
|
155
|
+
win.webContents.on('did-start-loading', () => {
|
|
156
|
+
if (!loaded[part]) return;
|
|
157
|
+
loaded[part] = false;
|
|
158
|
+
hovered[part] = false;
|
|
159
|
+
if (part === 'tag') { endPress(true); if (win.isVisible()) win.hide(); }
|
|
160
|
+
closeCard();
|
|
161
|
+
});
|
|
162
|
+
wins[part] = win;
|
|
163
|
+
if (part === 'tag') placeTag();
|
|
164
|
+
load(win, part);
|
|
165
|
+
return win;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const cancelClose = () => { if (closing) { clearTimer(closing); closing = null; } };
|
|
169
|
+
|
|
170
|
+
function closeCard() {
|
|
171
|
+
cancelClose();
|
|
172
|
+
open = false;
|
|
173
|
+
hovered.card = false;
|
|
174
|
+
if (alive('card') && wins.card.isVisible()) wins.card.hide();
|
|
175
|
+
send('tag', 'corner-tag:open', false);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const showCard = () => {
|
|
179
|
+
if (open && tagUp() && loaded.card && cardSize && alive('card') && !wins.card.isVisible()) wins.card.showInactive();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
function openCard() {
|
|
183
|
+
if (!tag || press?.moved || !tagUp()) return;
|
|
184
|
+
open = true;
|
|
185
|
+
send('tag', 'corner-tag:open', true);
|
|
186
|
+
watch();
|
|
187
|
+
reconsider();
|
|
188
|
+
if (!alive('card')) { make('card'); return; }
|
|
189
|
+
sendState();
|
|
190
|
+
placeCard();
|
|
191
|
+
showCard();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* --------------------- where the pointer really is ---------------------- */
|
|
195
|
+
// The page says when the pointer comes and goes, and that is trusted, but a
|
|
196
|
+
// boundary event can go missing, so while the list is open the real cursor
|
|
197
|
+
// is read a few times a second and wins. Nothing is read otherwise.
|
|
198
|
+
const within = (pt, r) => !!r && pt.x >= r.x && pt.x < r.x + r.width && pt.y >= r.y && pt.y < r.y + r.height;
|
|
199
|
+
const drawn = (part) => (alive(part) && wins[part].isVisible() ? wins[part].getBounds() : null);
|
|
200
|
+
const readCursor = () => {
|
|
201
|
+
const pt = screen.getCursorScreenPoint?.();
|
|
202
|
+
if (!pt || press) return false;
|
|
203
|
+
for (const part of ['tag', 'card']) hovered[part] = within(pt, drawn(part));
|
|
204
|
+
return true;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
function watch() {
|
|
208
|
+
if (watching || gone) return;
|
|
209
|
+
const tick = () => {
|
|
210
|
+
watching = null;
|
|
211
|
+
if (gone) return;
|
|
212
|
+
readCursor();
|
|
213
|
+
reconsider();
|
|
214
|
+
if (open || hovered.tag || hovered.card) watching = setTimer(tick, 200);
|
|
215
|
+
};
|
|
216
|
+
watching = setTimer(tick, 200);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Whether the open list should start, keep or drop its countdown to shutting.
|
|
220
|
+
function reconsider() {
|
|
221
|
+
if (!open || hovered.tag || hovered.card) { cancelClose(); return; }
|
|
222
|
+
if (closing) return;
|
|
223
|
+
closing = setTimer(() => {
|
|
224
|
+
closing = null;
|
|
225
|
+
// The last word is the real cursor's: a pointer that came back without
|
|
226
|
+
// the page noticing keeps the list open.
|
|
227
|
+
readCursor();
|
|
228
|
+
if (!hovered.tag && !hovered.card) closeCard();
|
|
229
|
+
else watch();
|
|
230
|
+
}, CLOSE_AFTER_MS);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cancelOpen = () => { if (opening) { clearTimer(opening); opening = null; } };
|
|
234
|
+
|
|
235
|
+
// The pointer came onto the tag: open the list if it is still there in a
|
|
236
|
+
// moment. The real cursor has the last word, as it does for shutting.
|
|
237
|
+
function meanToOpen() {
|
|
238
|
+
if (open || opening || settling || press) return;
|
|
239
|
+
opening = setTimer(() => {
|
|
240
|
+
opening = null;
|
|
241
|
+
readCursor();
|
|
242
|
+
if (hovered.tag && !settling && !press) openCard();
|
|
243
|
+
}, OPEN_AFTER_MS);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ------------------------------- the press ------------------------------ */
|
|
247
|
+
// Where the cursor is now, against where it was when the button went down.
|
|
248
|
+
const follow = () => {
|
|
249
|
+
const pt = screen.getCursorScreenPoint?.();
|
|
250
|
+
if (!pt || !press) return;
|
|
251
|
+
const dx = pt.x - press.start.x;
|
|
252
|
+
const dy = pt.y - press.start.y;
|
|
253
|
+
if (!press.moved && Math.hypot(dx, dy) >= DRAG_FROM_PX) {
|
|
254
|
+
press.moved = true;
|
|
255
|
+
cancelOpen();
|
|
256
|
+
settling = true;
|
|
257
|
+
closeCard();
|
|
258
|
+
send('tag', 'corner-tag:dragging', true);
|
|
259
|
+
}
|
|
260
|
+
if (press.moved && alive('tag')) {
|
|
261
|
+
tag = { ...tag, x: press.from.x + dx, y: press.from.y + dy };
|
|
262
|
+
wins.tag.setPosition(Math.round(tag.x), Math.round(tag.y));
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
function step() {
|
|
267
|
+
if (!press || gone) return;
|
|
268
|
+
press.timer = null;
|
|
269
|
+
follow();
|
|
270
|
+
if (now() - press.at > PRESS_GIVES_UP_MS) { endPress(true); return; }
|
|
271
|
+
press.timer = setTimer(step, FRAME_MS);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// The button came up (or the press was taken away). A drag lands where the
|
|
275
|
+
// cursor left it and is remembered; a press that never moved is a click.
|
|
276
|
+
function endPress(cancelled) {
|
|
277
|
+
if (!press) return;
|
|
278
|
+
if (press.timer) clearTimer(press.timer);
|
|
279
|
+
press.timer = null;
|
|
280
|
+
const p = press;
|
|
281
|
+
if (!cancelled) follow();
|
|
282
|
+
press = null;
|
|
283
|
+
if (p.moved) {
|
|
284
|
+
tag = { ...restingSpot({ x: tag.x, y: tag.y }, tagSize, areas()), ...tagSize };
|
|
285
|
+
if (alive('tag')) wins.tag.setBounds(rounded(tag));
|
|
286
|
+
saveConfig(config, { cornerTagSpot: { x: Math.round(tag.x), y: Math.round(tag.y) } });
|
|
287
|
+
send('tag', 'corner-tag:dragging', false);
|
|
288
|
+
} else if (!cancelled) {
|
|
289
|
+
cancelOpen();
|
|
290
|
+
goTo(null);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Everything that is not on screen is back where it starts: the list shut,
|
|
295
|
+
// no press half-done, so the tag comes back exactly as it was first drawn.
|
|
296
|
+
const reset = () => {
|
|
297
|
+
endPress(true);
|
|
298
|
+
cancelOpen();
|
|
299
|
+
settling = false;
|
|
300
|
+
closeCard();
|
|
301
|
+
hovered.tag = false;
|
|
302
|
+
send('tag', 'corner-tag:reset', null);
|
|
303
|
+
if (alive('tag') && tag) wins.tag.setBounds(rounded(tag));
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
function refresh() {
|
|
307
|
+
if (gone) return;
|
|
308
|
+
if (wake) { clearTimer(wake); wake = null; }
|
|
309
|
+
const until = config.cornerTagHiddenUntil ?? 0;
|
|
310
|
+
// A hide comes back by itself the moment it runs out.
|
|
311
|
+
if (until > now()) wake = setTimer(refresh, until - now() + 50);
|
|
312
|
+
if (!shouldShow()) {
|
|
313
|
+
if (alive('tag') && wins.tag.isVisible()) { wins.tag.hide(); reset(); }
|
|
314
|
+
else if (open) closeCard();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (!alive('tag')) { make('tag'); return; }
|
|
318
|
+
sendState();
|
|
319
|
+
if (loaded.tag && !wins.tag.isVisible()) wins.tag.showInactive();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const bringAppForward = () => {
|
|
323
|
+
if (!mainWindow || mainWindow.isDestroyed()) return false;
|
|
324
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
325
|
+
mainWindow.show();
|
|
326
|
+
mainWindow.focus();
|
|
327
|
+
return true;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Into the app: on a thread when a line of the list was picked, or just the
|
|
331
|
+
// app as it is when it was the tag.
|
|
332
|
+
function goTo(id) {
|
|
333
|
+
closeCard();
|
|
334
|
+
if (!bringAppForward()) return;
|
|
335
|
+
if (typeof id === 'string' && id) mainWindow.webContents.send('zero:open-item', { id });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const hide = (choice) => {
|
|
339
|
+
const until = hiddenUntil(choice, now());
|
|
340
|
+
if (!until) return;
|
|
341
|
+
saveConfig(config, { cornerTagHiddenUntil: until });
|
|
342
|
+
refresh();
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const openSettings = () => {
|
|
346
|
+
closeCard();
|
|
347
|
+
if (bringAppForward()) mainWindow.webContents.send('zero:open-settings', { pane: 'general' });
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const partOf = (p) => (p?.part === 'card' ? 'card' : 'tag');
|
|
351
|
+
|
|
352
|
+
/* ------------------------------ from the app's page ---------------------- */
|
|
353
|
+
ipcMain.handle('zero:corner-tag', (_e, state) => {
|
|
354
|
+
ready = Array.isArray(state?.ready) ? state.ready : [];
|
|
355
|
+
working = Number.isFinite(state?.working) ? state.working : 0;
|
|
356
|
+
refresh();
|
|
357
|
+
return null;
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
/* ------------------------------ from the tag's pages --------------------- */
|
|
361
|
+
ipcMain.handle('corner-tag:ready', (_e, p) => {
|
|
362
|
+
const part = partOf(p);
|
|
363
|
+
loaded[part] = true;
|
|
364
|
+
sendState();
|
|
365
|
+
if (part === 'tag') refresh();
|
|
366
|
+
else if (open) { placeCard(); showCard(); }
|
|
367
|
+
return null;
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
ipcMain.handle('corner-tag:size', (_e, p) => {
|
|
371
|
+
if (!p || !(p.width > 0) || !(p.height > 0)) return null;
|
|
372
|
+
const size = { width: Math.ceil(p.width), height: Math.ceil(p.height) };
|
|
373
|
+
if (partOf(p) === 'card') {
|
|
374
|
+
cardSize = size;
|
|
375
|
+
if (open) { placeCard(); showCard(); }
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
tagSize = size;
|
|
379
|
+
// Nothing moves under the pointer while it is dragging.
|
|
380
|
+
if (press?.moved) return null;
|
|
381
|
+
// The first measure places it where she left it. Every one after keeps
|
|
382
|
+
// the edge nearest its corner still as the words change length.
|
|
383
|
+
tag = tag && measured ? keepCorner(tag, tagSize, areaOf(tag)) : null;
|
|
384
|
+
measured = true;
|
|
385
|
+
placeTag();
|
|
386
|
+
if (open) placeCard();
|
|
387
|
+
return null;
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
ipcMain.handle('corner-tag:hover', (_e, p) => {
|
|
391
|
+
const part = partOf(p);
|
|
392
|
+
hovered[part] = !!p?.inside;
|
|
393
|
+
if (part === 'tag') {
|
|
394
|
+
if (p?.inside) meanToOpen();
|
|
395
|
+
else { settling = false; cancelOpen(); }
|
|
396
|
+
}
|
|
397
|
+
reconsider();
|
|
398
|
+
if (p?.inside) watch();
|
|
399
|
+
return null;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
ipcMain.handle('corner-tag:toggle', () => {
|
|
403
|
+
if (open) closeCard(); else openCard();
|
|
404
|
+
return null;
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// `at` is where the button went down, from the page. The message can arrive
|
|
408
|
+
// after the cursor has already moved, and starting from the cursor then put
|
|
409
|
+
// the tag about 30 px off under it. A press on a tag that has since hidden
|
|
410
|
+
// starts nothing.
|
|
411
|
+
ipcMain.handle('corner-tag:press', (_e, at) => {
|
|
412
|
+
const pt = Number.isFinite(at?.x) && Number.isFinite(at?.y) ? { x: at.x, y: at.y } : screen.getCursorScreenPoint?.();
|
|
413
|
+
if (!pt || !tag || !tagUp() || press) return null;
|
|
414
|
+
press = { start: pt, from: { x: tag.x, y: tag.y }, moved: false, at: now(), timer: null };
|
|
415
|
+
press.timer = setTimer(step, FRAME_MS);
|
|
416
|
+
return null;
|
|
417
|
+
});
|
|
418
|
+
ipcMain.handle('corner-tag:release', (_e, p) => { endPress(!!p?.cancelled); return null; });
|
|
419
|
+
|
|
420
|
+
ipcMain.handle('corner-tag:go', (_e, id) => { goTo(id); return null; });
|
|
421
|
+
ipcMain.handle('corner-tag:hide', (_e, choice) => { hide(choice); return null; });
|
|
422
|
+
ipcMain.handle('corner-tag:settings', () => { openSettings(); return null; });
|
|
423
|
+
|
|
424
|
+
// A right-click on the tag offers the same choices as the list does.
|
|
425
|
+
ipcMain.handle('corner-tag:menu', () => {
|
|
426
|
+
if (!alive('tag')) return null;
|
|
427
|
+
closeCard();
|
|
428
|
+
Menu.buildFromTemplate([
|
|
429
|
+
...HIDE_CHOICES.map((c) => ({ label: HIDE_LABELS[c.key], click: () => hide(c.key) })),
|
|
430
|
+
{ type: 'separator' },
|
|
431
|
+
{ label: 'Turn off in Settings…', click: openSettings },
|
|
432
|
+
]).popup({ window: wins.tag });
|
|
433
|
+
return null;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
/* ------------------------------ from the system -------------------------- */
|
|
437
|
+
// A screen that comes, goes or changes shape: the list shuts and the tag is
|
|
438
|
+
// pulled back onto a screen if it was left hanging off one.
|
|
439
|
+
const reseat = () => {
|
|
440
|
+
if (gone) return;
|
|
441
|
+
closeCard();
|
|
442
|
+
if (!tag || press) return;
|
|
443
|
+
tag = { ...restingSpot({ x: tag.x, y: tag.y }, tagSize, areas()), ...tagSize };
|
|
444
|
+
if (alive('tag')) wins.tag.setBounds(rounded(tag));
|
|
445
|
+
};
|
|
446
|
+
// After sleep the screens may have changed and a hide may have run out.
|
|
447
|
+
const woke = () => { reseat(); refresh(); };
|
|
448
|
+
|
|
449
|
+
const onFocus = () => refresh();
|
|
450
|
+
// The app's window is gone, and the tag goes with it. Left behind, it would
|
|
451
|
+
// hold the app open with nothing behind it and nothing it could open.
|
|
452
|
+
const dispose = () => {
|
|
453
|
+
if (gone) return;
|
|
454
|
+
gone = true;
|
|
455
|
+
endPress(true);
|
|
456
|
+
cancelClose();
|
|
457
|
+
if (wake) { clearTimer(wake); wake = null; }
|
|
458
|
+
if (watching) { clearTimer(watching); watching = null; }
|
|
459
|
+
cancelOpen();
|
|
460
|
+
for (const part of ['tag', 'card']) if (alive(part)) wins[part].destroy();
|
|
461
|
+
for (const ch of CHANNELS) { try { ipcMain.removeHandler?.(ch); } catch {} }
|
|
462
|
+
try {
|
|
463
|
+
screen.removeListener?.('display-added', reseat);
|
|
464
|
+
screen.removeListener?.('display-removed', reseat);
|
|
465
|
+
screen.removeListener?.('display-metrics-changed', reseat);
|
|
466
|
+
powerMonitor?.removeListener?.('resume', woke);
|
|
467
|
+
powerMonitor?.removeListener?.('unlock-screen', woke);
|
|
468
|
+
} catch {}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
if (mainWindow) {
|
|
472
|
+
mainWindow.on('focus', onFocus);
|
|
473
|
+
mainWindow.on('blur', onFocus);
|
|
474
|
+
mainWindow.on('closed', dispose);
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
screen.on('display-added', reseat);
|
|
478
|
+
screen.on('display-removed', reseat);
|
|
479
|
+
screen.on('display-metrics-changed', reseat);
|
|
480
|
+
} catch {}
|
|
481
|
+
try { powerMonitor?.on?.('resume', woke); powerMonitor?.on?.('unlock-screen', woke); } catch {}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
refresh,
|
|
485
|
+
dispose,
|
|
486
|
+
// Tests, and nothing else.
|
|
487
|
+
_state: () => ({ ready: fresh(), working, tag, open, visible: alive('tag') && wins.tag.isVisible() }),
|
|
488
|
+
};
|
|
489
|
+
}
|
package/main/crash-report.mjs
CHANGED
|
@@ -63,15 +63,38 @@ export function report(kind, err, extra = {}) {
|
|
|
63
63
|
return write(payload);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// THE ONE KIND WHOSE PROCESS DOES NOT SURVIVE BEING REPORTED. `onUncaught`
|
|
67
|
+
// re-throws, by the decision three lines below where it does it, so the main
|
|
68
|
+
// process stops. posthog-node's `capture` only buffers (flushAt 20,
|
|
69
|
+
// flushInterval 10s), and the `will-quit` flush never runs for a process that
|
|
70
|
+
// died, so for this kind the file on disk is the only copy that can reach
|
|
71
|
+
// anybody. It is therefore kept even when the live send said it took the
|
|
72
|
+
// report: one report counted twice is a worse number, and no report at all is
|
|
73
|
+
// a worse app.
|
|
74
|
+
const DIES_REPORTING = new Set(['main-uncaught']);
|
|
75
|
+
|
|
66
76
|
function write(payload) {
|
|
67
77
|
try {
|
|
68
78
|
fs.mkdirSync(ctx.dir, { recursive: true });
|
|
69
79
|
const file = path.join(ctx.dir, `crash-${payload.ts.replace(/[:.]/g, '-')}-${Math.floor(Math.random() * 1e6)}.json`);
|
|
70
80
|
fs.writeFileSync(file, JSON.stringify(payload, null, 2));
|
|
71
81
|
prune();
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
82
|
+
// ON DISK FIRST, ALWAYS, because this can run while the process is dying
|
|
83
|
+
// and there is no time to wait on a socket. No await, no retry.
|
|
84
|
+
//
|
|
85
|
+
// AND THEN TAKEN BACK OFF IT IF IT WENT. The queue is for a report nobody
|
|
86
|
+
// has sent yet; a report that HAS been sent and stays in it is sent a
|
|
87
|
+
// second time by the next launch's `drainCrashes`, which is 6 of the 21
|
|
88
|
+
// crash events since the Oct 6 launch being 6 crashes counted twice, and
|
|
89
|
+
// three of them filed against the version that drained them rather than
|
|
90
|
+
// the one they happened on (PostHog, 2026-10-08).
|
|
91
|
+
if (transport) {
|
|
92
|
+
let took = false;
|
|
93
|
+
try { took = transport(payload) !== false; } catch { took = false; }
|
|
94
|
+
if (took && !DIES_REPORTING.has(payload.kind)) {
|
|
95
|
+
try { fs.unlinkSync(file); } catch {}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
75
98
|
return file;
|
|
76
99
|
} catch { return null; }
|
|
77
100
|
}
|
package/main/demo.mjs
CHANGED
|
@@ -34,9 +34,8 @@ import path from 'node:path';
|
|
|
34
34
|
import { spawn } from 'node:child_process';
|
|
35
35
|
import { DEMO_DOCS, DEMO_ENV, DEMO_PRODUCTS, demoLedgerLines } from '../shared/demo-world.mjs';
|
|
36
36
|
import { prepareHome } from '../shared/fresh-user-home.mjs';
|
|
37
|
-
import { Name } from '../shared/product-name.mjs';
|
|
38
37
|
import { relaunchCommand } from './fresh-user.mjs';
|
|
39
|
-
import { encodeProjectPath, storeRootEnv } from './store/home.mjs';
|
|
38
|
+
import { defaultStoreRoot, encodeProjectPath, storeRootEnv } from './store/home.mjs';
|
|
40
39
|
import { NAME, nameSlug } from '../shared/product-name.mjs';
|
|
41
40
|
|
|
42
41
|
/**
|
|
@@ -87,22 +86,21 @@ export function clearDemoHomes() {
|
|
|
87
86
|
/**
|
|
88
87
|
* Where the demo copy will keep its store, and where it will keep its own
|
|
89
88
|
* records. Both are derived from the throwaway home rather than configured,
|
|
90
|
-
* because the copy derives them the same way from the same home
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* because the copy derives them the same way from the same home. Both are
|
|
90
|
+
* `defaultStoreRoot` for that home with no inherited data directory: on
|
|
91
|
+
* macOS a folder of the app's name, on Linux the XDG path under the
|
|
92
|
+
* throwaway. Passing the machine's XDG_DATA_HOME would seed the demo into
|
|
93
|
+
* the real store.
|
|
93
94
|
*
|
|
94
|
-
* THE FOLDER IS
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* at the old folder: the demo copy would have written its store to one place
|
|
98
|
-
* and looked for it in another. Two literals that have to agree is the defect;
|
|
99
|
-
* one import is the fix. */
|
|
95
|
+
* THE FOLDER IS THAT FUNCTION, not a second spelling of it. A literal next
|
|
96
|
+
* to the default is how the demo once wrote its store in one place and
|
|
97
|
+
* looked for it in another. */
|
|
100
98
|
export function demoPaths(home) {
|
|
101
|
-
const storeRoot =
|
|
99
|
+
const storeRoot = defaultStoreRoot({ home, env: {} });
|
|
102
100
|
return {
|
|
103
101
|
storeRoot,
|
|
104
102
|
accountRoot: path.join(storeRoot, 'accounts', DEMO_ACCOUNT),
|
|
105
|
-
appHome:
|
|
103
|
+
appHome: storeRoot,
|
|
106
104
|
};
|
|
107
105
|
}
|
|
108
106
|
|
|
@@ -192,7 +190,12 @@ export function seedDemoStore(home, { now = Date.now() } = {}) {
|
|
|
192
190
|
* standing in an invented store. */
|
|
193
191
|
export function demoEnv(home, env = process.env) {
|
|
194
192
|
const { appHome } = demoPaths(home);
|
|
195
|
-
|
|
193
|
+
const next = { ...env, HOME: home, CFFIXED_USER_HOME: home, ...storeRootEnv(appHome), [DEMO_ENV]: '1' };
|
|
194
|
+
// The copy's store is defaultStoreRoot of ITS home. The parent's
|
|
195
|
+
// XDG_DATA_HOME is the real data directory, and leaving it set points the
|
|
196
|
+
// demo at that directory. The other base directories go for the same reason.
|
|
197
|
+
for (const key of ['XDG_DATA_HOME', 'XDG_CONFIG_HOME', 'XDG_STATE_HOME', 'XDG_CACHE_HOME']) delete next[key];
|
|
198
|
+
return next;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
/**
|