@rightkit/system-ui 0.0.0-stage → 0.1.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +36 -2
  5. package/dist/activityLease.d.ts +1 -0
  6. package/dist/activityLease.js +20 -0
  7. package/dist/api.d.ts +1011 -0
  8. package/dist/api.js +296 -0
  9. package/dist/chart.d.ts +14 -0
  10. package/dist/chart.js +94 -0
  11. package/dist/components/Chart.d.ts +40 -0
  12. package/dist/components/Chart.js +109 -0
  13. package/dist/env.d.ts +60 -0
  14. package/dist/index.d.ts +26 -0
  15. package/dist/index.js +28 -0
  16. package/dist/kept.d.ts +3 -0
  17. package/dist/kept.js +21 -0
  18. package/dist/lib/chatKind.d.ts +4 -0
  19. package/dist/lib/chatKind.js +15 -0
  20. package/dist/lib/legal.d.ts +26 -0
  21. package/dist/lib/legal.js +39 -0
  22. package/dist/lib/onboardingSettings.d.ts +2 -0
  23. package/dist/lib/onboardingSettings.js +17 -0
  24. package/dist/lib/qr.d.ts +9 -0
  25. package/dist/lib/qr.js +286 -0
  26. package/dist/lib/rk.d.ts +6 -0
  27. package/dist/lib/rk.js +30 -0
  28. package/dist/metrics.d.ts +24 -0
  29. package/dist/metrics.js +76 -0
  30. package/dist/onboarding/flow.d.ts +31 -0
  31. package/dist/onboarding/flow.js +81 -0
  32. package/dist/onboarding/gate.d.ts +8 -0
  33. package/dist/onboarding/gate.js +52 -0
  34. package/dist/styles.css +8 -0
  35. package/dist/views/Activity.d.ts +3 -0
  36. package/dist/views/Activity.js +111 -0
  37. package/dist/views/Apps.d.ts +1 -0
  38. package/dist/views/Apps.js +478 -0
  39. package/dist/views/Chat.d.ts +1 -0
  40. package/dist/views/Chat.js +255 -0
  41. package/dist/views/ChromeSnapshots.d.ts +6 -0
  42. package/dist/views/ChromeSnapshots.js +19 -0
  43. package/dist/views/Cleanup.d.ts +4 -0
  44. package/dist/views/Cleanup.js +291 -0
  45. package/dist/views/DriveHealth.d.ts +14 -0
  46. package/dist/views/DriveHealth.js +81 -0
  47. package/dist/views/Duplicates.d.ts +2 -0
  48. package/dist/views/Duplicates.js +99 -0
  49. package/dist/views/LauncherSettings.d.ts +63 -0
  50. package/dist/views/LauncherSettings.js +242 -0
  51. package/dist/views/NearbySettings.d.ts +117 -0
  52. package/dist/views/NearbySettings.js +237 -0
  53. package/dist/views/Storage.d.ts +9 -0
  54. package/dist/views/Storage.js +916 -0
  55. package/dist/views/activity.css +94 -0
  56. package/dist/views/apps.css +98 -0
  57. package/dist/views/chat.css +61 -0
  58. package/dist/views/cleanup.css +49 -0
  59. package/dist/views/health.css +17 -0
  60. package/dist/views/membrane.css +23 -0
  61. package/dist/views/monitor.css +99 -0
  62. package/dist/views/settings.css +176 -0
  63. package/dist/views/settingsKit.d.ts +14 -0
  64. package/dist/views/settingsKit.js +43 -0
  65. package/dist/views/storage.css +121 -0
  66. package/dist/views/storageEstimate.d.ts +6 -0
  67. package/dist/views/storageEstimate.js +7 -0
  68. package/dist/visible.d.ts +3 -0
  69. package/dist/visible.js +42 -0
  70. package/package.json +110 -3
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ // Generated entry: every module whose exports do not collide with an earlier one.
2
+ // Every module is also importable directly by subpath, e.g. `<package>/lib/foo`.
3
+ export * from "./activityLease.js";
4
+ export * from "./api.js";
5
+ export * from "./chart.js";
6
+ export * from "./components/Chart.js";
7
+ export * from "./kept.js";
8
+ export * from "./lib/chatKind.js";
9
+ export * from "./lib/legal.js";
10
+ export * from "./lib/onboardingSettings.js";
11
+ export * from "./lib/qr.js";
12
+ export * from "./lib/rk.js";
13
+ export * from "./metrics.js";
14
+ export * from "./onboarding/flow.js";
15
+ export * from "./onboarding/gate.js";
16
+ export * from "./views/Activity.js";
17
+ export * from "./views/Apps.js";
18
+ export * from "./views/Chat.js";
19
+ export * from "./views/ChromeSnapshots.js";
20
+ export * from "./views/Cleanup.js";
21
+ export * from "./views/DriveHealth.js";
22
+ export * from "./views/Duplicates.js";
23
+ export * from "./views/LauncherSettings.js";
24
+ export * from "./views/NearbySettings.js";
25
+ export * from "./views/Storage.js";
26
+ export * from "./views/settingsKit.js";
27
+ export * from "./views/storageEstimate.js";
28
+ export * from "./visible.js";
package/dist/kept.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** The hub's web view is destroyed on window close, so a view's selection is kept here (best effort). */
2
+ export declare const readKept: <T>(key: string) => T | null;
3
+ export declare const writeKept: (key: string, value: unknown | null) => void;
package/dist/kept.js ADDED
@@ -0,0 +1,21 @@
1
+ /** The hub's web view is destroyed on window close, so a view's selection is kept here (best effort). */
2
+ export const readKept = (key) => {
3
+ try {
4
+ const raw = localStorage.getItem(key);
5
+ return raw == null ? null : JSON.parse(raw);
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ };
11
+ export const writeKept = (key, value) => {
12
+ try {
13
+ if (value == null)
14
+ localStorage.removeItem(key);
15
+ else
16
+ localStorage.setItem(key, JSON.stringify(value));
17
+ }
18
+ catch {
19
+ // Storage unavailable: the selection is lost on close, as before.
20
+ }
21
+ };
@@ -0,0 +1,4 @@
1
+ /** The chat kinds the host app knows, in the order grouped lists show them. Any other kind is listed after these. */
2
+ export declare const CHAT_KIND_ORDER: readonly string[];
3
+ /** The name a chat kind shows as: Claude, Codex, Muse, or any other kind with its first letter capitalised. */
4
+ export declare function chatKindName(kind: string): string;
@@ -0,0 +1,15 @@
1
+ /** The chat kinds the host app knows, in the order grouped lists show them. Any other kind is listed after these. */
2
+ export const CHAT_KIND_ORDER = ["claude", "codex", "muse"];
3
+ /** The name a chat kind shows as: Claude, Codex, Muse, or any other kind with its first letter capitalised. */
4
+ export function chatKindName(kind) {
5
+ switch (kind) {
6
+ case "claude":
7
+ return "Claude";
8
+ case "codex":
9
+ return "Codex";
10
+ case "muse":
11
+ return "Muse";
12
+ default:
13
+ return kind.charAt(0).toUpperCase() + kind.slice(1);
14
+ }
15
+ }
@@ -0,0 +1,26 @@
1
+ import { type LegalStepConfig } from "@rightkit/onboarding";
2
+ /** What the host's `virtual:rightkit-legal` build module provides (@rightkit/legal's Vite plugin). */
3
+ export interface HostLegal {
4
+ LEGAL_DOCUMENTS: readonly {
5
+ title: string;
6
+ content: string;
7
+ }[];
8
+ LEGAL_VERSION: string;
9
+ LEGAL_DOCUMENTS_HASH: string;
10
+ LEGAL_RECEIPT_DOCUMENTS: readonly {
11
+ id: string;
12
+ role: string;
13
+ version: string;
14
+ sha256: string;
15
+ treatment: string;
16
+ material: boolean;
17
+ }[];
18
+ }
19
+ export declare let LEGAL_VERSION: string;
20
+ export declare let LEGAL_DOCUMENTS_HASH: string;
21
+ export declare let LEGAL_RECEIPT_DOCUMENTS: HostLegal["LEGAL_RECEIPT_DOCUMENTS"];
22
+ export declare function configureLegal(legal: HostLegal): void;
23
+ /** The agreement on file, read and recorded by the native receipt service, which stamps the time (src-tauri/src/legal.rs). */
24
+ export declare const legalReceipt: import("@rightkit/onboarding").LegalReceiptPort;
25
+ /** The legal gate: suite default policies (rendered at build time), host colours from the shell tokens. */
26
+ export declare const legalStepConfig: () => LegalStepConfig;
@@ -0,0 +1,39 @@
1
+ import { policyGateDocuments } from "@rightkit/legal-ui";
2
+ import { createNativeLegalReceiptPort } from "@rightkit/onboarding";
3
+ import { invoke } from "@tauri-apps/api/core";
4
+ import { createShell } from "@rightkit/tauri-shell";
5
+ // Set once by the host at startup (configureLegal(await import("virtual:rightkit-legal"))): a published package cannot import a
6
+ // build-time module of the app that installs it. ES module exports are live bindings, so importers see the configured values.
7
+ export let LEGAL_VERSION;
8
+ export let LEGAL_DOCUMENTS_HASH;
9
+ export let LEGAL_RECEIPT_DOCUMENTS = [];
10
+ let documents = [];
11
+ export function configureLegal(legal) {
12
+ if (!legal || typeof legal.LEGAL_VERSION !== "string" || !Array.isArray(legal.LEGAL_DOCUMENTS)) {
13
+ throw new TypeError("configureLegal: pass the host's virtual:rightkit-legal module");
14
+ }
15
+ LEGAL_VERSION = legal.LEGAL_VERSION;
16
+ LEGAL_DOCUMENTS_HASH = legal.LEGAL_DOCUMENTS_HASH;
17
+ LEGAL_RECEIPT_DOCUMENTS = legal.LEGAL_RECEIPT_DOCUMENTS;
18
+ documents = policyGateDocuments(legal.LEGAL_DOCUMENTS);
19
+ }
20
+ /** The agreement on file, read and recorded by the native receipt service, which stamps the time (src-tauri/src/legal.rs). */
21
+ export const legalReceipt = createNativeLegalReceiptPort({ invoke });
22
+ /** The legal gate: suite default policies (rendered at build time), host colours from the shell tokens. */
23
+ export const legalStepConfig = () => ({
24
+ appName: "Pulse",
25
+ trustLine: "Pulse watches your computer's storage and your agent chats locally. Nothing leaves this computer unless you turn a feature on.",
26
+ documents,
27
+ agreementStatement: `I have read and agree to the ${documents.map((d) => d.title).join(", ")}.`,
28
+ theme: {
29
+ bg: "var(--rk-plane)",
30
+ surface: "var(--rk-panel)",
31
+ border: "var(--rk-line-strong)",
32
+ text: "var(--rk-ink)",
33
+ muted: "var(--rk-ink-2)",
34
+ accent: "var(--rk-accent)",
35
+ accentContrast: "var(--rk-accent-ink)",
36
+ },
37
+ // The hub window closes; the notch keeps running (the hub's lifecycle is fixed).
38
+ onQuit: () => void createShell().window.close().catch(() => undefined),
39
+ });
@@ -0,0 +1,2 @@
1
+ import type { SettingsBackend } from "@rightkit/onboarding";
2
+ export declare const onboardingSettings: SettingsBackend;
@@ -0,0 +1,17 @@
1
+ import { invoke } from "@tauri-apps/api/core";
2
+ // The onboarding's own progress, kept in the hub's `hub-onboarding.json` document (a frontend-owned
3
+ // rightkit-app-settings Document). The notch's settings (docs/settings.md) hold only `onboardingDone`. The legal
4
+ // agreement is not kept here: the native receipt service records it (lib/legal.ts); the shared component only copies
5
+ // that record into `onboarding.state`, and only the one-time carry-over of an older record reads it (src-tauri/src/legal.rs).
6
+ const NAME = "onboarding";
7
+ export const onboardingSettings = {
8
+ load: () => invoke("hub_document_load", { name: NAME }),
9
+ patch: async (patch) => {
10
+ try {
11
+ return await invoke("hub_document_patch", { name: NAME, patch });
12
+ }
13
+ catch {
14
+ throw new Error("Pulse could not save your setup progress.");
15
+ }
16
+ },
17
+ };
@@ -0,0 +1,9 @@
1
+ /** A small dependency-free QR Code encoder: byte mode, error correction M, versions 1 to 20, mask chosen by penalty.
2
+ * The pairing QR holds a public URI of about 250 bytes (version 11). Returns the module matrix (true = dark). */
3
+ /** The module matrix for `text` (UTF-8, byte mode). Throws when it does not fit version 20 at level M. */
4
+ export declare function qrMatrix(text: string): boolean[][];
5
+ /** The matrix as one SVG path (quiet zone of 4 modules), for rendering at any size. */
6
+ export declare function qrSvgPath(matrix: boolean[][]): {
7
+ path: string;
8
+ size: number;
9
+ };
package/dist/lib/qr.js ADDED
@@ -0,0 +1,286 @@
1
+ /** A small dependency-free QR Code encoder: byte mode, error correction M, versions 1 to 20, mask chosen by penalty.
2
+ * The pairing QR holds a public URI of about 250 bytes (version 11). Returns the module matrix (true = dark). */
3
+ const ECC_PER_BLOCK_M = [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26];
4
+ const BLOCKS_M = [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16];
5
+ const MAX_VERSION = 20;
6
+ function rawModules(ver) {
7
+ let r = (16 * ver + 128) * ver + 64;
8
+ if (ver >= 2) {
9
+ const n = Math.floor(ver / 7) + 2;
10
+ r -= (25 * n - 10) * n - 55;
11
+ if (ver >= 7)
12
+ r -= 36;
13
+ }
14
+ return r;
15
+ }
16
+ const dataCodewords = (ver) => Math.floor(rawModules(ver) / 8) - ECC_PER_BLOCK_M[ver] * BLOCKS_M[ver];
17
+ // GF(256) arithmetic, polynomial 0x11D.
18
+ const EXP = new Array(512);
19
+ const LOG = new Array(256);
20
+ (() => {
21
+ let x = 1;
22
+ for (let i = 0; i < 255; i++) {
23
+ EXP[i] = x;
24
+ LOG[x] = i;
25
+ x <<= 1;
26
+ if (x & 0x100)
27
+ x ^= 0x11d;
28
+ }
29
+ for (let i = 255; i < 512; i++)
30
+ EXP[i] = EXP[i - 255];
31
+ })();
32
+ const mul = (a, b) => (a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]]);
33
+ function generator(degree) {
34
+ let poly = [1];
35
+ for (let i = 0; i < degree; i++) {
36
+ const next = new Array(poly.length + 1).fill(0);
37
+ for (let j = 0; j < poly.length; j++) {
38
+ next[j] ^= poly[j];
39
+ next[j + 1] ^= mul(poly[j], EXP[i]);
40
+ }
41
+ poly = next;
42
+ }
43
+ return poly;
44
+ }
45
+ function remainder(data, gen) {
46
+ const rem = new Array(gen.length - 1).fill(0);
47
+ for (const byte of data) {
48
+ const factor = byte ^ rem.shift();
49
+ rem.push(0);
50
+ for (let i = 0; i < rem.length; i++)
51
+ rem[i] ^= mul(gen[i + 1], factor);
52
+ }
53
+ return rem;
54
+ }
55
+ function interleave(data, ver) {
56
+ const blocks = BLOCKS_M[ver];
57
+ const eccLen = ECC_PER_BLOCK_M[ver];
58
+ const rawCodewords = Math.floor(rawModules(ver) / 8);
59
+ const shortBlocks = blocks - (rawCodewords % blocks);
60
+ const shortLen = Math.floor(rawCodewords / blocks);
61
+ const gen = generator(eccLen);
62
+ const dataBlocks = [];
63
+ const eccBlocks = [];
64
+ for (let i = 0, k = 0; i < blocks; i++) {
65
+ const len = shortLen - eccLen + (i < shortBlocks ? 0 : 1);
66
+ const chunk = data.slice(k, k + len);
67
+ k += len;
68
+ dataBlocks.push(chunk);
69
+ eccBlocks.push(remainder(chunk, gen));
70
+ }
71
+ const out = [];
72
+ for (let i = 0; i <= shortLen - eccLen; i++) {
73
+ dataBlocks.forEach((b) => { if (i < b.length)
74
+ out.push(b[i]); });
75
+ }
76
+ for (let i = 0; i < eccLen; i++)
77
+ eccBlocks.forEach((b) => out.push(b[i]));
78
+ return out;
79
+ }
80
+ /** The module matrix for `text` (UTF-8, byte mode). Throws when it does not fit version 20 at level M. */
81
+ export function qrMatrix(text) {
82
+ const bytes = Array.from(new TextEncoder().encode(text));
83
+ let ver = 1;
84
+ for (; ver <= MAX_VERSION; ver++) {
85
+ const countBits = ver < 10 ? 8 : 16;
86
+ if (4 + countBits + bytes.length * 8 <= dataCodewords(ver) * 8)
87
+ break;
88
+ }
89
+ if (ver > MAX_VERSION)
90
+ throw new Error("QR payload is too long");
91
+ const bits = [];
92
+ const put = (value, len) => { for (let i = len - 1; i >= 0; i--)
93
+ bits.push((value >>> i) & 1); };
94
+ put(0b0100, 4);
95
+ put(bytes.length, ver < 10 ? 8 : 16);
96
+ bytes.forEach((b) => put(b, 8));
97
+ const capacity = dataCodewords(ver) * 8;
98
+ put(0, Math.min(4, capacity - bits.length));
99
+ while (bits.length % 8)
100
+ bits.push(0);
101
+ for (let pad = 0xec; bits.length < capacity; pad ^= 0xec ^ 0x11)
102
+ put(pad, 8);
103
+ const data = [];
104
+ for (let i = 0; i < bits.length; i += 8)
105
+ data.push(parseInt(bits.slice(i, i + 8).join(""), 2));
106
+ const codewords = interleave(data, ver);
107
+ const size = ver * 4 + 17;
108
+ const grid = Array.from({ length: size }, () => new Array(size).fill(false));
109
+ const fn = Array.from({ length: size }, () => new Array(size).fill(false));
110
+ const set = (x, y, dark) => {
111
+ if (x < 0 || y < 0 || x >= size || y >= size)
112
+ return;
113
+ grid[y][x] = dark;
114
+ fn[y][x] = true;
115
+ };
116
+ for (let i = 0; i < size; i++) {
117
+ set(6, i, i % 2 === 0);
118
+ set(i, 6, i % 2 === 0);
119
+ }
120
+ const finder = (cx, cy) => {
121
+ for (let dy = -4; dy <= 4; dy++)
122
+ for (let dx = -4; dx <= 4; dx++) {
123
+ const d = Math.max(Math.abs(dx), Math.abs(dy));
124
+ set(cx + dx, cy + dy, d !== 2 && d !== 4);
125
+ }
126
+ };
127
+ finder(3, 3);
128
+ finder(size - 4, 3);
129
+ finder(3, size - 4);
130
+ if (ver >= 2) {
131
+ const n = Math.floor(ver / 7) + 2;
132
+ const step = ver === 32 ? 26 : Math.ceil((ver * 4 + 4) / (n * 2 - 2)) * 2;
133
+ const pos = [6];
134
+ for (let p = size - 7; pos.length < n; p -= step)
135
+ pos.splice(1, 0, p);
136
+ for (let i = 0; i < n; i++)
137
+ for (let j = 0; j < n; j++) {
138
+ if ((i === 0 && j === 0) || (i === 0 && j === n - 1) || (i === n - 1 && j === 0))
139
+ continue;
140
+ for (let dy = -2; dy <= 2; dy++)
141
+ for (let dx = -2; dx <= 2; dx++) {
142
+ set(pos[i] + dx, pos[j] + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1);
143
+ }
144
+ }
145
+ }
146
+ const formatBits = (mask) => {
147
+ const data = (0b00 << 3) | mask; // level M = 00
148
+ let rem = data;
149
+ for (let i = 0; i < 10; i++)
150
+ rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
151
+ return ((data << 10) | rem) ^ 0x5412;
152
+ };
153
+ const drawFormat = (mask) => {
154
+ const b = formatBits(mask);
155
+ const bit = (i) => ((b >>> i) & 1) !== 0;
156
+ for (let i = 0; i <= 5; i++)
157
+ set(8, i, bit(i));
158
+ set(8, 7, bit(6));
159
+ set(8, 8, bit(7));
160
+ set(7, 8, bit(8));
161
+ for (let i = 9; i < 15; i++)
162
+ set(14 - i, 8, bit(i));
163
+ for (let i = 0; i < 8; i++)
164
+ set(size - 1 - i, 8, bit(i));
165
+ for (let i = 8; i < 15; i++)
166
+ set(8, size - 15 + i, bit(i));
167
+ set(8, size - 8, true);
168
+ };
169
+ drawFormat(0);
170
+ if (ver >= 7) {
171
+ let rem = ver;
172
+ for (let i = 0; i < 12; i++)
173
+ rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
174
+ const b = (ver << 12) | rem;
175
+ for (let i = 0; i < 18; i++) {
176
+ const dark = ((b >>> i) & 1) !== 0;
177
+ const a = size - 11 + (i % 3);
178
+ const c = Math.floor(i / 3);
179
+ set(a, c, dark);
180
+ set(c, a, dark);
181
+ }
182
+ }
183
+ // Place the data bits in the zigzag, skipping function modules.
184
+ const total = codewords.length * 8;
185
+ let k = 0;
186
+ for (let right = size - 1; right >= 1; right -= 2) {
187
+ if (right === 6)
188
+ right = 5;
189
+ for (let v = 0; v < size; v++)
190
+ for (let j = 0; j < 2; j++) {
191
+ const x = right - j;
192
+ const y = ((right + 1) & 2) === 0 ? size - 1 - v : v;
193
+ if (!fn[y][x] && k < total) {
194
+ grid[y][x] = ((codewords[k >>> 3] >>> (7 - (k & 7))) & 1) !== 0;
195
+ k++;
196
+ }
197
+ }
198
+ }
199
+ const masks = [
200
+ (x, y) => (x + y) % 2 === 0,
201
+ (_x, y) => y % 2 === 0,
202
+ (x) => x % 3 === 0,
203
+ (x, y) => (x + y) % 3 === 0,
204
+ (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0,
205
+ (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
206
+ (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
207
+ (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
208
+ ];
209
+ const apply = (mask) => {
210
+ for (let y = 0; y < size; y++)
211
+ for (let x = 0; x < size; x++) {
212
+ if (!fn[y][x] && masks[mask](x, y))
213
+ grid[y][x] = !grid[y][x];
214
+ }
215
+ };
216
+ const penalty = () => {
217
+ let score = 0;
218
+ const lines = [grid, grid[0].map((_, x) => grid.map((row) => row[x]))];
219
+ for (const rows of lines) {
220
+ for (const row of rows) {
221
+ let run = 1;
222
+ for (let i = 1; i <= size; i++) {
223
+ if (i < size && row[i] === row[i - 1])
224
+ run++;
225
+ else {
226
+ if (run >= 5)
227
+ score += run - 2;
228
+ run = 1;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ for (let y = 0; y < size - 1; y++)
234
+ for (let x = 0; x < size - 1; x++) {
235
+ const c = grid[y][x];
236
+ if (c === grid[y][x + 1] && c === grid[y + 1][x] && c === grid[y + 1][x + 1])
237
+ score += 3;
238
+ }
239
+ const pat = [true, false, true, true, true, false, true, false, false, false, false];
240
+ const rev = [...pat].reverse();
241
+ for (const rows of lines) {
242
+ for (const row of rows) {
243
+ for (let i = 0; i + 11 <= size; i++) {
244
+ const win = row.slice(i, i + 11);
245
+ if (win.every((v, n) => v === pat[n]) || win.every((v, n) => v === rev[n]))
246
+ score += 40;
247
+ }
248
+ }
249
+ }
250
+ const dark = grid.reduce((n, row) => n + row.filter(Boolean).length, 0);
251
+ score += Math.floor(Math.abs(dark * 20 - size * size * 10) / (size * size)) * 10;
252
+ return score;
253
+ };
254
+ let best = 0;
255
+ let bestScore = Infinity;
256
+ for (let m = 0; m < 8; m++) {
257
+ apply(m);
258
+ drawFormat(m);
259
+ const s = penalty();
260
+ if (s < bestScore) {
261
+ bestScore = s;
262
+ best = m;
263
+ }
264
+ apply(m);
265
+ }
266
+ apply(best);
267
+ drawFormat(best);
268
+ return grid;
269
+ }
270
+ /** The matrix as one SVG path (quiet zone of 4 modules), for rendering at any size. */
271
+ export function qrSvgPath(matrix) {
272
+ const n = matrix.length;
273
+ let path = "";
274
+ matrix.forEach((row, y) => {
275
+ for (let x = 0; x < n; x++) {
276
+ if (!row[x])
277
+ continue;
278
+ let end = x;
279
+ while (end + 1 < n && row[end + 1])
280
+ end++;
281
+ path += `M${x + 4} ${y + 4}h${end - x + 1}v1h-${end - x + 1}z`;
282
+ x = end;
283
+ }
284
+ });
285
+ return { path, size: n + 8 };
286
+ }
@@ -0,0 +1,6 @@
1
+ export declare const native: import("@rightkit/tauri").RightKitTauriClient<unknown, unknown, Record<string, unknown>, unknown>;
2
+ export declare const logs: import("@rightkit/logs").LogsClient;
3
+ /** This install's RightKit device id (not the devicelink pairing identity). */
4
+ export declare const deviceId: () => Promise<string>;
5
+ /** Sends uncaught errors and unhandled rejections to the native crash command, which logs the kind and place only, never the message. */
6
+ export declare function installWebviewLog(): void;
package/dist/lib/rk.js ADDED
@@ -0,0 +1,30 @@
1
+ // RightKit's mandatory webview clients over the hub's real Tauri `invoke`:
2
+ // - `native` (@rightkit/tauri): typed commands for the device id, logs and diagnostics. Licensing is
3
+ // native and has its own client (`hub/src/lib/license.ts`).
4
+ // - `logs` (@rightkit/logs): batched structured events to `rightkit_logs_write`, written into the
5
+ // hub log (`hub.jsonl`) by the native side. Only the fields listed here ever reach disk.
6
+ // Webview errors go through `installWindowCrashCapture` to `rightkit_crash_window_error`.
7
+ import { invoke } from "@tauri-apps/api/core";
8
+ import { createLogsClient, installWindowCrashCapture } from "@rightkit/logs";
9
+ import { createRightKitTauriClient, standardRightKitDeviceGetOrCreate } from "@rightkit/tauri";
10
+ export const native = createRightKitTauriClient({
11
+ invoke,
12
+ deviceGetOrCreate: standardRightKitDeviceGetOrCreate,
13
+ });
14
+ export const logs = createLogsClient({
15
+ invoke,
16
+ allowedFields: ["surface", "kind", "name", "source", "line", "count", "code", "duration_ms"],
17
+ });
18
+ /** This install's RightKit device id (not the devicelink pairing identity). */
19
+ export const deviceId = () => native.device.getOrCreate();
20
+ let installed = false;
21
+ /** Sends uncaught errors and unhandled rejections to the native crash command, which logs the kind and place only, never the message. */
22
+ export function installWebviewLog() {
23
+ if (installed)
24
+ return;
25
+ installed = true;
26
+ installWindowCrashCapture({ invoke });
27
+ window.addEventListener("pagehide", () => void logs.flush());
28
+ logs.info("webview_started", { surface: "hub" });
29
+ void deviceId().catch(() => logs.warn("device_id_unavailable", { surface: "hub" }));
30
+ }
@@ -0,0 +1,24 @@
1
+ export interface Sample {
2
+ /** Unix time, ms. */
3
+ ts: number;
4
+ /** Overall CPU, 0–100. */
5
+ cpu: number;
6
+ memUsed: number;
7
+ memTotal: number;
8
+ swapUsed: number;
9
+ swapTotal: number;
10
+ pressure: string | null;
11
+ /** Bytes per second; null while unread. */
12
+ netDown: number | null;
13
+ netUp: number | null;
14
+ }
15
+ export interface Metrics {
16
+ samples: Sample[];
17
+ latest: Sample | null;
18
+ }
19
+ /** The last 30 minutes of samples (oldest first) and the newest one. */
20
+ export declare function useMetrics(): Metrics;
21
+ /** Samples from the last `minutes`, measured back from the newest sample. */
22
+ export declare function lastMinutes(samples: Sample[], minutes: number): Sample[];
23
+ export declare const memPercent: (s: Sample) => number;
24
+ export declare const swapPercent: (s: Sample) => number;
@@ -0,0 +1,76 @@
1
+ // Live system history for the graphs. The Rust side (metrics_history.rs)
2
+ // samples every 2 s and keeps 30 minutes, even while the window is closed.
3
+ // This module loads that once, then polls only what is new while something
4
+ // is subscribed.
5
+ import { useSyncExternalStore } from "react";
6
+ import { invoke } from "@tauri-apps/api/core";
7
+ import { clearVisibleInterval, visibleInterval } from "./visible.js";
8
+ const POLL_MS = 2000;
9
+ const KEEP_MS = 30 * 60 * 1000;
10
+ let state = { samples: [], latest: null };
11
+ const listeners = new Set();
12
+ let timer = null;
13
+ let inFlight = false;
14
+ // Visibility lease: renewed with every visible poll (2 s, ttl 6 s). The engine samples every
15
+ // 2 s while any lease lives and every 10 s otherwise; a hidden hub stops renewing and it lapses.
16
+ const CLIENT_ID = `hub-${Math.random().toString(36).slice(2)}`;
17
+ const LEASE_TTL_MS = 6000;
18
+ function merge(fresh) {
19
+ if (fresh.length === 0)
20
+ return;
21
+ const last = state.latest?.ts ?? 0;
22
+ const added = fresh.filter((s) => s.ts > last);
23
+ if (added.length === 0)
24
+ return;
25
+ const all = state.samples.concat(added);
26
+ const cutoff = all[all.length - 1].ts - KEEP_MS;
27
+ const samples = all.filter((s) => s.ts >= cutoff);
28
+ state = { samples, latest: samples[samples.length - 1] };
29
+ listeners.forEach((l) => l());
30
+ }
31
+ async function poll() {
32
+ if (inFlight)
33
+ return;
34
+ inFlight = true;
35
+ try {
36
+ // Lease first so the engine's immediate sample is in the read that follows.
37
+ await invoke("metrics_lease", { client: CLIENT_ID, ttlMs: LEASE_TTL_MS }).catch(() => { });
38
+ const since = state.latest?.ts;
39
+ merge(await invoke("metrics_history", { sinceMs: since ?? null }));
40
+ }
41
+ catch {
42
+ // The next tick tries again; the graphs keep what they have.
43
+ }
44
+ finally {
45
+ inFlight = false;
46
+ }
47
+ }
48
+ function subscribe(listener) {
49
+ listeners.add(listener);
50
+ if (listeners.size === 1) {
51
+ poll();
52
+ timer = visibleInterval(poll, POLL_MS);
53
+ }
54
+ return () => {
55
+ listeners.delete(listener);
56
+ if (listeners.size === 0 && timer != null) {
57
+ clearVisibleInterval(timer);
58
+ timer = null;
59
+ }
60
+ };
61
+ }
62
+ const snapshot = () => state;
63
+ /** The last 30 minutes of samples (oldest first) and the newest one. */
64
+ export function useMetrics() {
65
+ return useSyncExternalStore(subscribe, snapshot);
66
+ }
67
+ /** Samples from the last `minutes`, measured back from the newest sample. */
68
+ export function lastMinutes(samples, minutes) {
69
+ if (samples.length === 0)
70
+ return samples;
71
+ const cutoff = samples[samples.length - 1].ts - minutes * 60_000;
72
+ const first = samples.findIndex((s) => s.ts >= cutoff);
73
+ return first <= 0 ? samples : samples.slice(first);
74
+ }
75
+ export const memPercent = (s) => (s.memTotal > 0 ? (s.memUsed / s.memTotal) * 100 : 0);
76
+ export const swapPercent = (s) => (s.swapTotal > 0 ? (s.swapUsed / s.swapTotal) * 100 : 0);
@@ -0,0 +1,31 @@
1
+ export declare const FLOW_KEY = "pulseFlow";
2
+ export declare const FLOW_VERSION = 1;
3
+ export type Draft = Record<string, boolean | string>;
4
+ export interface Commit {
5
+ /** `writing` was saved before the first write: a window that finds it after a restart treats it as unfinished. */
6
+ status: "idle" | "writing" | "partial" | "done";
7
+ /** Setting key to why its write was not confirmed. */
8
+ failed: Record<string, string>;
9
+ }
10
+ export interface Flow {
11
+ version: typeof FLOW_VERSION;
12
+ /** A first run starts every switch on; a re-run starts from the current values. Fixed when the flow is made. */
13
+ first: boolean;
14
+ draft: Draft;
15
+ /** Permission ids the user chose to decide later. */
16
+ deferred: string[];
17
+ commit: Commit;
18
+ }
19
+ export declare const freshFlow: (first: boolean) => Flow;
20
+ export declare function loadFlow(): Promise<Flow | null>;
21
+ export declare function saveFlow(flow: Flow): Promise<void>;
22
+ type SettingValue = boolean | string;
23
+ export declare function readSettings(): Promise<Record<string, unknown> | null>;
24
+ /** One confirmed write: the notch's typed answer, or, when it has not answered yet, its published state. */
25
+ export declare function writeSetting(key: string, value: SettingValue): Promise<string | null>;
26
+ /**
27
+ * Writes each `[key, value]` that the notch does not already hold, one at a time, and returns the keys whose write was
28
+ * not confirmed. Nothing is assumed: the next call reads the published state again, so a retry repeats only what failed.
29
+ */
30
+ export declare function writeAll(plan: [string, SettingValue][]): Promise<Record<string, string>>;
31
+ export {};