isorite 0.12.0 → 0.13.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 (166) hide show
  1. package/README.md +6 -5
  2. package/dist/main.js +5688 -8903
  3. package/docs/setup-for-agents.md +44 -102
  4. package/docs/setup.md +23 -87
  5. package/docs/terminal.md +54 -55
  6. package/guide/AGENTS.md +4 -0
  7. package/guide/atlas/find-what-it-rests-on.md +9 -0
  8. package/guide/atlas/the-atlas.md +21 -0
  9. package/guide/atlas/the-switches/satellites.svg +17 -0
  10. package/guide/atlas/the-switches.md +27 -0
  11. package/guide/iso/agents-md.md +13 -0
  12. package/guide/iso/artifacts/the-guide.csv +8 -0
  13. package/guide/iso/artifacts/the-guide.html +35 -0
  14. package/guide/iso/artifacts.md +15 -0
  15. package/guide/iso/ask-iso.md +7 -0
  16. package/guide/iso/give-it-notes.md +9 -0
  17. package/guide/iso/in-hand.md +18 -0
  18. package/guide/iso/iso-writes-marked.md +15 -0
  19. package/guide/iso/iso.md +17 -0
  20. package/guide/iso/let-it-write.md +11 -0
  21. package/guide/iso/write-agents-md.md +11 -0
  22. package/guide/notes/a-note-is-markdown.md +17 -0
  23. package/guide/notes/a-notes-details.md +23 -0
  24. package/guide/notes/diagrams-and-code.md +43 -0
  25. package/guide/notes/every-change-is-kept.md +14 -0
  26. package/guide/notes/folders.md +11 -0
  27. package/guide/notes/go-back-a-version.md +9 -0
  28. package/guide/notes/link-your-notes.md +9 -0
  29. package/guide/notes/links-and-their-kinds.md +23 -0
  30. package/guide/notes/make-move-delete.md +9 -0
  31. package/guide/notes/write-in-your-notes.md +9 -0
  32. package/guide/out-of-the-base/web-and-code-links.md +13 -0
  33. package/guide/questions-and-problems/ask-a-question.md +9 -0
  34. package/guide/questions-and-problems/how-iso-reads-you.md +9 -0
  35. package/guide/questions-and-problems/problems.md +15 -0
  36. package/guide/questions-and-problems/questions.md +27 -0
  37. package/guide/questions-and-problems/resolve-a-question.md +9 -0
  38. package/guide/questions-and-problems/why-markdown-links.md +7 -0
  39. package/guide/solve-this-one.md +11 -0
  40. package/guide/start-here.md +39 -0
  41. package/guide/terminal/let-the-agent-remember.md +9 -0
  42. package/guide/terminal/memory.md +13 -0
  43. package/guide/terminal/your-own-agent.md +24 -0
  44. package/guide/your-notes.md +6 -0
  45. package/guide/your-own-base/keeping-a-base-well.md +16 -0
  46. package/guide/your-own-base/starting-a-base.md +21 -0
  47. package/package.json +4 -7
  48. package/web/_app/immutable/assets/0.BEjn1Bfl.css +1 -0
  49. package/web/_app/immutable/assets/4.fOHsAKBF.css +1 -0
  50. package/web/_app/immutable/assets/drawn.D6lJzDcc.css +1 -0
  51. package/web/_app/immutable/chunks/{BK6pLgdi.js → 3fCuxe18.js} +1 -1
  52. package/web/_app/immutable/chunks/{D5XlJBy8.js → B-DzK7Gb.js} +1 -1
  53. package/web/_app/immutable/chunks/{BuUzVkG2.js → B35sQTcK.js} +1 -1
  54. package/web/_app/immutable/chunks/{D4YEq54N.js → B6uHgLdT.js} +1 -1
  55. package/web/_app/immutable/chunks/{Dh1lBxfx.js → BA78_vo5.js} +1 -1
  56. package/web/_app/immutable/chunks/{tUSDgbpy.js → BCGaqnao.js} +1 -1
  57. package/web/_app/immutable/chunks/BCyLp7pS.js +1 -0
  58. package/web/_app/immutable/chunks/{EMo1IeTW.js → BK31dpt6.js} +1 -1
  59. package/web/_app/immutable/chunks/BOxykeIc.js +1 -0
  60. package/web/_app/immutable/chunks/{DlXrOzlh.js → BT_btfh0.js} +1 -1
  61. package/web/_app/immutable/chunks/{Xm0YTnxV.js → BWE_HS0c.js} +1 -1
  62. package/web/_app/immutable/chunks/{BLmF1B2i.js → BXBhaEho.js} +1 -1
  63. package/web/_app/immutable/chunks/{BAclEN0O.js → Bb_N98K9.js} +1 -1
  64. package/web/_app/immutable/chunks/{gPtCGHiy.js → Bd-WxALw.js} +1 -1
  65. package/web/_app/immutable/chunks/{BA8Hpxhg.js → BhEqJU15.js} +1 -1
  66. package/web/_app/immutable/chunks/{DkxkvOjx.js → BiATTH-R.js} +1 -1
  67. package/web/_app/immutable/chunks/{D5vocsTl.js → Bj-5n5Pb.js} +1 -1
  68. package/web/_app/immutable/chunks/{DRX-TQzj.js → BqipEJXw.js} +1 -1
  69. package/web/_app/immutable/chunks/{CiMp12cC.js → Bt-fHApV.js} +1 -1
  70. package/web/_app/immutable/chunks/{DXTK-eyo.js → BvPxdlBc.js} +1 -1
  71. package/web/_app/immutable/chunks/{DfCZPwp1.js → C2npZzkn.js} +1 -1
  72. package/web/_app/immutable/chunks/{DF89zyH-.js → C5Z-Y7Lu.js} +1 -1
  73. package/web/_app/immutable/chunks/{CD6xPodA.js → C8h1oeAZ.js} +1 -1
  74. package/web/_app/immutable/chunks/{DDCg82xL.js → C8nK3Jvr.js} +1 -1
  75. package/web/_app/immutable/chunks/{CFEI5lV9.js → CAlIuau0.js} +1 -1
  76. package/web/_app/immutable/chunks/{9z49435t.js → CFf_dAzX.js} +1 -1
  77. package/web/_app/immutable/chunks/{D3-rqfsM.js → CMIt1Ma8.js} +1 -1
  78. package/web/_app/immutable/chunks/{-2Meesge.js → CMikI1NP.js} +3 -3
  79. package/web/_app/immutable/chunks/{Czz9qOh8.js → CT4_O9X_.js} +1 -1
  80. package/web/_app/immutable/chunks/{DX4bJJO0.js → CTDYUZPz.js} +1 -1
  81. package/web/_app/immutable/chunks/{By4S_gy5.js → CdY1G_wk.js} +1 -1
  82. package/web/_app/immutable/chunks/Cd_SqIWq.js +523 -0
  83. package/web/_app/immutable/chunks/{DBC2xjOo.js → CdmC_l9F.js} +1 -1
  84. package/web/_app/immutable/chunks/{DbNETcPs.js → Cf-5I5_6.js} +1 -1
  85. package/web/_app/immutable/chunks/Cl5gQm2e.js +1 -0
  86. package/web/_app/immutable/chunks/{BWVMfAMh.js → CryuQgNh.js} +1 -1
  87. package/web/_app/immutable/chunks/{CT_Ry4q8.js → Ct_EK1xu.js} +1 -1
  88. package/web/_app/immutable/chunks/{BymwHy7o.js → CtkUy_15.js} +1 -1
  89. package/web/_app/immutable/chunks/{Dpa4Syyy.js → CzYPMYd4.js} +1 -1
  90. package/web/_app/immutable/chunks/{RlApGeew.js → D1LK3xRu.js} +1 -1
  91. package/web/_app/immutable/chunks/{DXK7PB4g.js → D6sVhEz3.js} +1 -1
  92. package/web/_app/immutable/chunks/{jTXVas70.js → D9JwUycj.js} +1 -1
  93. package/web/_app/immutable/chunks/{rghE0CVz.js → DCZYeMlH.js} +1 -1
  94. package/web/_app/immutable/chunks/{RFxekmX-.js → DDAG55ce.js} +1 -1
  95. package/web/_app/immutable/chunks/{CyFLqlvP.js → DFuPQaNa.js} +1 -1
  96. package/web/_app/immutable/chunks/{BXAOBcPb.js → DHJ8FcFD.js} +1 -1
  97. package/web/_app/immutable/chunks/{Bt7VOT5H.js → DKqMzyyE.js} +1 -1
  98. package/web/_app/immutable/chunks/{Bq2GBAJ0.js → DRcTlHZP.js} +1 -1
  99. package/web/_app/immutable/chunks/{C_amgxV1.js → DjUnonHY.js} +1 -1
  100. package/web/_app/immutable/chunks/{whhiiiga.js → Djr0h_v1.js} +1 -1
  101. package/web/_app/immutable/chunks/{vfDvz3qP.js → DrVtdHUW.js} +1 -1
  102. package/web/_app/immutable/chunks/{Uo6irDQW.js → O98JkczU.js} +74 -74
  103. package/web/_app/immutable/chunks/{oWM_kgYq.js → PsfIQCJ1.js} +1 -1
  104. package/web/_app/immutable/chunks/{Cl5zRP_n.js → TanDZqdf.js} +1 -1
  105. package/web/_app/immutable/chunks/{D95RKsvN.js → UDPiRmKc.js} +1 -1
  106. package/web/_app/immutable/chunks/WLe0MT1c.js +1 -0
  107. package/web/_app/immutable/chunks/{BTclWEfx.js → ZwM5k8r0.js} +1 -1
  108. package/web/_app/immutable/chunks/e2LGvGEb.js +3 -0
  109. package/web/_app/immutable/chunks/ej_0XxR7.js +1 -0
  110. package/web/_app/immutable/chunks/{CjtKPNlv.js → kG4HctVa.js} +1 -1
  111. package/web/_app/immutable/chunks/{CQd9D8Nc.js → l1LsK1sB.js} +1 -1
  112. package/web/_app/immutable/chunks/{D_jbSWJl.js → s_hW4oA1.js} +1 -1
  113. package/web/_app/immutable/entry/app.DM_AWBgH.js +2 -0
  114. package/web/_app/immutable/entry/start.BLhIKDcR.js +1 -0
  115. package/web/_app/immutable/nodes/0.xSbkmYPn.js +2 -0
  116. package/web/_app/immutable/nodes/1.BD2wJdoH.js +1 -0
  117. package/web/_app/immutable/nodes/2.shPqwtS_.js +1 -0
  118. package/web/_app/immutable/nodes/3.e_5kWaf_.js +1 -0
  119. package/web/_app/immutable/nodes/4.BEMEU6Jz.js +15 -0
  120. package/web/_app/immutable/nodes/5.DxaO-e4O.js +1 -0
  121. package/web/_app/immutable/nodes/6.C9ia8cDm.js +1 -0
  122. package/web/_app/version.json +1 -1
  123. package/web/index.html +16 -19
  124. package/dist/core.js +0 -2464
  125. package/plugin/assets/mark-lapis.png +0 -0
  126. package/plugin/assets/mark-quartz.png +0 -0
  127. package/plugin/assets/wordmark-lapis.png +0 -0
  128. package/plugin/assets/wordmark-quartz.png +0 -0
  129. package/plugin/isorite.ts +0 -65
  130. package/plugin/logo.ts +0 -99
  131. package/plugin/notepage.tsx +0 -165
  132. package/plugin/notes.ts +0 -10
  133. package/plugin/noteview.ts +0 -60
  134. package/plugin/oneatlas.ts +0 -83
  135. package/plugin/palette.ts +0 -21
  136. package/plugin/play.ts +0 -104
  137. package/plugin/server.ts +0 -76
  138. package/plugin/sources.ts +0 -18
  139. package/plugin/tui.tsx +0 -556
  140. package/plugin/wordmark.ts +0 -180
  141. package/web/_app/immutable/assets/0.BXPxx6Q0.css +0 -1
  142. package/web/_app/immutable/assets/4.CUdclsPi.css +0 -1
  143. package/web/_app/immutable/assets/5.rBvyRuST.css +0 -1
  144. package/web/_app/immutable/assets/Drawing.Q7J9MyIo.css +0 -1
  145. package/web/_app/immutable/assets/NoteEditor.S3q5lmKq.css +0 -1
  146. package/web/_app/immutable/assets/Timeline.LsRXYIBv.css +0 -1
  147. package/web/_app/immutable/chunks/4kgFUGOH.js +0 -1
  148. package/web/_app/immutable/chunks/8SS1q0nk.js +0 -7
  149. package/web/_app/immutable/chunks/Bk_JinGf.js +0 -1
  150. package/web/_app/immutable/chunks/BqrMZ9MX.js +0 -1
  151. package/web/_app/immutable/chunks/DThNJvmp.js +0 -1
  152. package/web/_app/immutable/chunks/DbeMVOH4.js +0 -1
  153. package/web/_app/immutable/chunks/DsQj0LyB.js +0 -523
  154. package/web/_app/immutable/chunks/DshHzxgH.js +0 -1
  155. package/web/_app/immutable/chunks/Iqr2CudW.js +0 -3
  156. package/web/_app/immutable/entry/app.CvK8zypr.js +0 -2
  157. package/web/_app/immutable/entry/start.Dcgm6sQd.js +0 -1
  158. package/web/_app/immutable/nodes/0.0TdRBEoR.js +0 -26
  159. package/web/_app/immutable/nodes/1.Dchqgt4j.js +0 -1
  160. package/web/_app/immutable/nodes/2.C0CMUdSu.js +0 -1
  161. package/web/_app/immutable/nodes/3.MjtWDMWd.js +0 -1
  162. package/web/_app/immutable/nodes/4.hhS0SQta.js +0 -2
  163. package/web/_app/immutable/nodes/5.C33_cw2J.js +0 -9
  164. package/web/_app/immutable/nodes/6.owNfwwRl.js +0 -1
  165. package/web/_app/immutable/nodes/7.BpxHVHoY.js +0 -1
  166. /package/web/_app/immutable/assets/{7.C00vyVPo.css → 6.C00vyVPo.css} +0 -0
Binary file
Binary file
Binary file
Binary file
package/plugin/isorite.ts DELETED
@@ -1,65 +0,0 @@
1
- // The isorite server, as the plugin reaches it: over HTTP, as the browser does.
2
- // `isorite term` names it in ISORITE_URL.
3
-
4
- /** This harness's name as the server knows its agent. */
5
- export const AGENT = "opencode";
6
-
7
- export const url = (process.env.ISORITE_URL ?? "http://127.0.0.1:4767").replace(/\/$/, "");
8
-
9
- export interface Session { name: string; study: boolean; device: string; token: string }
10
- export interface Listing { notes: { id: string; title: string; dir: string }[]; dirs: string[] }
11
-
12
- /** A GET on the server's API; a refusal is thrown with the server's own words. */
13
- export async function get<T>(path: string): Promise<T> {
14
- const response = await fetch(url + path);
15
- const body = (await response.json().catch(() => ({}))) as T & { error?: string };
16
- if (!response.ok) throw new Error(body.error ?? `${response.status} from ${url + path}`);
17
- return body;
18
- }
19
-
20
- let token: Promise<string> | null = null;
21
-
22
- /** A POST to the server, made as its own page makes one: from its origin, with the token it hands out. */
23
- export async function post<T>(path: string, body: unknown): Promise<T> {
24
- token ??= get<Session>("/api/session").then((s) => s.token, (e: Error) => { token = null; throw e; });
25
- const response = await fetch(url + path, {
26
- method: "POST",
27
- headers: { "Content-Type": "application/json", "x-isorite-token": await token, Origin: new URL(url).origin },
28
- body: JSON.stringify(body),
29
- });
30
- const answer = (await response.json().catch(() => ({}))) as T & { error?: string };
31
- if (!response.ok) throw new Error(answer.error ?? `${response.status} from ${url + path}`);
32
- return answer;
33
- }
34
-
35
- /** A tool the server offers an agent, as OpenAI's API describes one. */
36
- export interface ToolDef { type: "function"; function: { name: string; description: string; parameters: Record<string, unknown> } }
37
-
38
- /** What the splash says of the base: its name and how many notes. */
39
- export async function base(): Promise<{ name: string; notes: number }> {
40
- const [session, listing] = await Promise.all([get<Session>("/api/session"), get<Listing>("/api/notes")]);
41
- return { name: session.name, notes: listing.notes.length };
42
- }
43
-
44
- /** The server's events, as the browser hears them, until the signal aborts; heard again after a dropped stream. */
45
- export function listen(on: (type: string, data: Record<string, unknown>) => void, signal: AbortSignal): void {
46
- void (async () => {
47
- while (!signal.aborted) {
48
- try {
49
- // As an agent: the server counts only browser pages as watching.
50
- const response = await fetch(`${url}/api/events?agent=${AGENT}`, { signal });
51
- const decoder = new TextDecoder();
52
- let buffer = "";
53
- for await (const chunk of response.body as unknown as AsyncIterable<Uint8Array>) {
54
- buffer += decoder.decode(chunk, { stream: true });
55
- for (let end; (end = buffer.indexOf("\n\n")) >= 0; buffer = buffer.slice(end + 2)) {
56
- const block = buffer.slice(0, end);
57
- const type = block.match(/^event: (.*)$/m)?.[1], data = block.match(/^data: (.*)$/m)?.[1];
58
- if (type && data) { try { on(type, JSON.parse(data) as Record<string, unknown>); } catch { /* not ours */ } }
59
- }
60
- }
61
- } catch { /* the server went; try again */ }
62
- if (!signal.aborted) await new Promise((r) => setTimeout(r, 2000));
63
- }
64
- })();
65
- }
package/plugin/logo.ts DELETED
@@ -1,99 +0,0 @@
1
- // The isorite mark as `Terminal-Splash` draws it, in terminal cells.
2
- //
3
- // The board draws it on a grid 10 squares wide and 15 tall. Every shape is a full
4
- // square or a corner half of one, and a square is two cells wide and one row tall,
5
- // so the mark is 20 cells by 15 rows. The two top rows are in growth (gold), the
6
- // rest in the text colour.
7
- //
8
- // A corner half is drawn, by default, as one of Powerline's triangles in a square one
9
- // cell wide (`triangle`): terminals draw those themselves, from corner to corner and
10
- // meeting the full blocks (xterm.js and so VS Code, Windows Terminal, kitty, Ghostty,
11
- // WezTerm), where they leave ◢ ◣ ◤ ◥ to the font, which draws them inside the cell.
12
- // Its edges are straight, but as steep as a cell is tall (about 63°), not 45°: no
13
- // character a terminal draws itself makes a 45° edge. The other styles keep the
14
- // board's two-cell square: `legacy` draws a half with the wedges of Symbols for
15
- // Legacy Computing (45°, but the wedges split a cell in thirds, so an edge bends
16
- // where its cells meet, and xterm.js leaves them to the font); `quadrant` as a stair
17
- // of quarter blocks; `simple` as one font triangle and a space. `ISORITE_LOGO`
18
- // chooses.
19
-
20
- export type Corner = "tl" | "tr" | "bl" | "br";
21
-
22
- /** A square of the grid: whole, or a half with one corner left empty. */
23
- export interface Shape {
24
- readonly x: number;
25
- readonly y: number;
26
- readonly empty?: Corner;
27
- readonly growth?: boolean;
28
- }
29
-
30
- export type Style = "legacy" | "quadrant" | "simple" | "triangle";
31
-
32
- /** One run of cells in one colour. */
33
- export interface Run {
34
- readonly text: string;
35
- readonly growth: boolean;
36
- }
37
-
38
- export const width = 10;
39
- export const height = 15;
40
-
41
- const half = (x: number, y: number, empty: Corner, growth = false): Shape => ({ x, y, empty, growth });
42
- const full = (x: number, y: number, growth = false): Shape => ({ x, y, growth });
43
-
44
- // From the board's SVG: a triangle is named by the corner of its square that it leaves empty.
45
- export const shapes: readonly Shape[] = [
46
- half(4, 0, "tl", true), full(5, 0, true), half(6, 0, "tr", true),
47
- half(4, 1, "bl", true), full(5, 1, true), half(6, 1, "br", true),
48
- half(9, 3, "br"), half(8, 3, "tl"),
49
- half(8, 4, "br"), half(7, 4, "tl"),
50
- half(7, 5, "br"), half(6, 5, "tl"),
51
- half(6, 6, "br"), half(5, 6, "tl"),
52
- half(0, 7, "bl"), half(1, 8, "bl"), half(2, 9, "bl"), half(3, 10, "bl"), half(4, 11, "bl"),
53
- half(1, 7, "tr"), half(2, 8, "tr"), half(3, 9, "tr"), half(4, 10, "tr"),
54
- full(5, 7), full(5, 8), full(5, 9), full(5, 10), full(5, 11), full(5, 12), full(5, 13), full(5, 14),
55
- ];
56
-
57
- export const glyphs: Record<Style, Record<Corner | "full", string>> = {
58
- // Each pair is one square: the left cell, then the right.
59
- legacy: {
60
- full: "██",
61
- tl: "\u{1FB4A}\u{1FB42}", // lower right block, from the lower left to the upper right
62
- tr: "\u{1FB4D}\u{1FB3F}", // lower left block, from the upper left to the lower right
63
- bl: "\u{1FB63}\u{1FB55}", // upper right block, from the upper left to the lower right
64
- br: "\u{1FB60}\u{1FB58}", // upper left block, from the lower left to the upper right
65
- },
66
- quadrant: { full: "██", tl: "▗▟", tr: "▙▖", bl: "▝▜", br: "▛▘" },
67
- simple: { full: "██", tl: " ◢", tr: "◣ ", bl: " ◥", br: "◤ " },
68
- // A square one cell: its halves straight from corner to corner, steeper than 45° as a cell is taller than wide.
69
- // Powerline's triangles, which terminals draw themselves, edge to edge (xterm.js and so VS Code, Windows
70
- // Terminal, kitty, Ghostty, WezTerm): ◢ ◣ ◥ ◤ are left to the font, which draws them inside the cell.
71
- triangle: { full: "█", tl: "\u{E0BA}", tr: "\u{E0B8}", bl: "\u{E0BE}", br: "\u{E0BC}" },
72
- };
73
-
74
- /** A square of the style left empty: as many spaces as it is cells wide. */
75
- export function blank(style: Style): string {
76
- return " ".repeat([...glyphs[style].full].length);
77
- }
78
-
79
- /** The mark's rows, each a list of runs, 10 cells wide in `triangle`, 20 in the others. */
80
- export function rows(style: Style = "quadrant"): Run[][] {
81
- const grid: (Shape | undefined)[][] = Array.from({ length: height }, () => Array(width).fill(undefined));
82
- for (const shape of shapes) grid[shape.y]![shape.x] = shape;
83
- return grid.map((row) => {
84
- const runs: Run[] = [];
85
- for (const shape of row) {
86
- const text = shape ? glyphs[style][shape.empty ?? "full"] : blank(style);
87
- const growth = shape?.growth ?? false;
88
- const last = runs.at(-1);
89
- if (last && (last.growth === growth || !text.trim())) runs[runs.length - 1] = { text: last.text + text, growth: last.growth };
90
- else runs.push({ text, growth });
91
- }
92
- return runs;
93
- });
94
- }
95
-
96
- /** The mark as plain lines, for tests and for terminals without colour. */
97
- export function lines(style: Style = "quadrant"): string[] {
98
- return rows(style).map((runs) => runs.map((run) => run.text).join(""));
99
- }
@@ -1,165 +0,0 @@
1
- /** @jsxImportSource @opentui/solid */
2
- // A note in the terminal (`Terminal-Note`): its blocks behind a gutter of who wrote them, links underlined, maths as
3
- // source, code on its own ground, a figure offered in the browser; the changes waiting to be kept between dotted
4
- // rules, one of them current, with its keys.
5
-
6
- import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core";
7
- import type { Entry } from "../dist/core.js";
8
- import { For, Show, type Accessor } from "solid-js";
9
- import { agentName } from "../dist/core.js";
10
- import type { Ground } from "./palette.ts";
11
- import type { Item, Mark, ViewBlock, ViewChange } from "./noteview.ts";
12
-
13
-
14
- export interface NoteData {
15
- id: string;
16
- title: string;
17
- /** The note's text, as it is (or was, at a moment of its timeline). */
18
- text: string;
19
- items: Item[];
20
- /** At a moment of the timeline: what it was, and how long ago. Read only. */
21
- at?: { said: string; ago: string };
22
- entries: Entry[];
23
- }
24
-
25
- const DOTS = "· ".repeat(38).trim();
26
- /** The widest a note's column is: its gutter, rail and about 76 characters of text. */
27
- const MEASURE = 84;
28
-
29
- /** A note's path as the boards write it: "physics / softening". */
30
- export function pathOf(id: string, title: string): string {
31
- return [...id.split("/").slice(0, -1).map((d) => d.replace(/-/g, " ")), title.toLowerCase()].join(" / ");
32
- }
33
-
34
- /** Who a change is from, as people say it. */
35
- export function fromWhom(who: string): string {
36
- return who.startsWith("iso:") ? "Iso" : agentName(who) || "you";
37
- }
38
-
39
- /** A run of a line: plain, or a link's text, underlined. */
40
- function runs(line: string): { text: string; link: boolean }[] {
41
- const out: { text: string; link: boolean }[] = [];
42
- let at = 0;
43
- for (const m of line.matchAll(/(?<!!)\[([^\]]+)\]\([^)]*\)/g)) {
44
- if (m.index! > at) out.push({ text: line.slice(at, m.index), link: false });
45
- out.push({ text: m[1]!, link: true });
46
- at = m.index! + m[0].length;
47
- }
48
- if (at < line.length) out.push({ text: line.slice(at), link: false });
49
- return out.length ? out : [{ text: line || " ", link: false }];
50
- }
51
-
52
- export function NotePage(props: { note: Accessor<NoteData | undefined>; current: Accessor<string>; cursor: Accessor<number>; asking: Accessor<boolean>; ground: Accessor<Ground>; base: string; scroll: (s: ScrollBoxRenderable) => void }) {
53
- const g = () => props.ground();
54
- const glyph = (mark: Mark | "waiting") =>
55
- mark === "you" ? { c: "■", fg: g().lattice } : mark === "outside" ? { c: "□", fg: g().lattice } : mark === "iso" ? { c: "◆", fg: g().growth }
56
- : mark === "agent" ? { c: "◆", fg: g().faint } : mark === "waiting" ? { c: "◇", fg: g().growth } : { c: " ", fg: g().faint };
57
-
58
- /** One row: the gutter, the rail (blue beside the block the cursor is on), and what is on it. */
59
- const Row = (p: { mark?: Mark | "waiting"; at?: boolean; children: unknown }) => (
60
- <box flexDirection="row" minWidth={0}>
61
- <text width={4} flexShrink={0} fg={glyph(p.mark ?? "").fg}>{` ${glyph(p.mark ?? "").c} `}</text>
62
- <box width={2} flexShrink={0} border={["left"]} borderColor={p.at ? g().lattice : g().line} />
63
- <box flexGrow={1} minWidth={0} paddingLeft={1} flexDirection="column">{p.children as never}</box>
64
- </box>
65
- );
66
- const Prose = (p: { text: string; bold?: boolean; fg?: string }) => (
67
- <For each={p.text.split("\n")}>
68
- {(line) => (
69
- <text wrapMode="word" fg={p.fg ?? g().text} attributes={p.bold ? TextAttributes.BOLD : 0}>
70
- <For each={runs(line)}>{(r) => (r.link ? <span style={{ fg: g().lattice, attributes: TextAttributes.UNDERLINE }}>{r.text}</span> : r.text)}</For>
71
- </text>
72
- )}
73
- </For>
74
- );
75
- const Change = (p: { c: ViewChange }) => {
76
- const on = () => props.current() === p.c.key;
77
- return (
78
- <box flexDirection="column">
79
- <Row mark="waiting"><text fg={g().growth} wrapMode="none">{DOTS}</text></Row>
80
- <Show when={!p.c.placed}><Row><text fg={g().faint} wrapMode="word">The place this was written for is no longer in the note.</text></Row></Show>
81
- <Show when={p.c.old !== undefined}><Row><text fg={g().faint} wrapMode="word" attributes={TextAttributes.STRIKETHROUGH}>{p.c.old}</text></Row></Show>
82
- <Row><Prose text={p.c.text.trim() || "(taken out)"} fg={on() ? g().text : g().soft} /></Row>
83
- <Row>
84
- <text wrapMode="none">
85
- <span style={{ fg: g().growth }}>{`◆ ${fromWhom(p.c.who)}`}</span>
86
- <span style={{ fg: g().faint }}>{" · not kept yet "}</span>
87
- <span style={{ fg: on() ? g().mal : g().faint }}>{"[k] keep "}</span>
88
- <span style={{ fg: on() ? g().growth : g().faint }}>{"[e] edit "}</span>
89
- <span style={{ fg: on() ? g().rose : g().faint }}>{"[r] remove"}</span>
90
- <Show when={!on()}><span style={{ fg: g().faint }}>{" [n] to choose it"}</span></Show>
91
- </text>
92
- </Row>
93
- <Show when={p.c.asked}><Row><text fg={g().faint} wrapMode="word">{`“${p.c.asked}”`}</text></Row></Show>
94
- <Row mark="waiting"><text fg={g().growth} wrapMode="none">{DOTS}</text></Row>
95
- <Row><text> </text></Row>
96
- </box>
97
- );
98
- };
99
-
100
- const blockIndex = (item: Item) => props.note()?.items.filter((i) => i.type === "block").indexOf(item as ViewBlock) ?? -1;
101
-
102
- return (
103
- <box flexDirection="column" width="100%" height="100%" backgroundColor={g().g0}>
104
- <Show when={props.note()} fallback={<text fg={g().faint} paddingLeft={4} paddingTop={1}>opening the note…</text>}>
105
- {(note) => (
106
- <>
107
- <box flexDirection="row" paddingLeft={4} paddingRight={4} paddingTop={1} flexShrink={0}>
108
- <text fg={g().text} wrapMode="none" flexShrink={1} minWidth={0}>{pathOf(note().id, note().title)}</text>
109
- <Show when={note().at}>{(at) => <text fg={g().growth} wrapMode="none" flexShrink={1} minWidth={0}>{` as it was ${at().ago}: ${at().said}`}</text>}</Show>
110
- <box flexGrow={1} />
111
- </box>
112
- <scrollbox ref={props.scroll} flexGrow={1} paddingLeft={4} paddingRight={4} paddingTop={1} scrollbarOptions={{ visible: false }}>
113
- {/* As the board: the text in a column of reading width, not the terminal's. */}
114
- <box flexDirection="column" maxWidth={MEASURE}>
115
- <For each={note().items}>
116
- {(item) => item.type === "change" ? <Change c={item} /> : (
117
- <box flexDirection="column" id={`block-${blockIndex(item)}`}>
118
- <Show when={item.kind === "code"} fallback={
119
- <Row mark={item.mark} at={blockIndex(item) === props.cursor()}>
120
- <Show when={item.kind === "figure"} fallback={
121
- <Show when={item.kind === "math"} fallback={<Prose text={item.text} bold={item.kind === "heading"} />}>
122
- <For each={item.text.split("\n")}>{(line) => <text wrapMode="word" fg={/^\$\$\s*$/.test(line) ? g().soft : g().text}>{line}</text>}</For>
123
- </Show>
124
- }>
125
- <text wrapMode="word">
126
- <span style={{ fg: g().faint }}>{`[ figure: ${/^!\[([^\]]*)\]/.exec(item.text)?.[1] || "untitled"} ] `}</span>
127
- <span style={{ fg: g().lattice, attributes: TextAttributes.UNDERLINE }}>{"[b] see it in the browser"}</span>
128
- </text>
129
- </Show>
130
- </Row>
131
- }>
132
- <Row mark={item.mark} at={blockIndex(item) === props.cursor()}>
133
- <box backgroundColor={g().g1} flexDirection="column" paddingLeft={1}>
134
- <text fg={g().faint}>{/^(```|~~~)\s*(\S*)/.exec(item.text)?.[2] || "code"}</text>
135
- <For each={item.text.split("\n").slice(1, -1)}>{(line) => <text fg={g().text} wrapMode="none">{line || " "}</text>}</For>
136
- </box>
137
- </Row>
138
- </Show>
139
- <Row><text> </text></Row>
140
- </box>
141
- )}
142
- </For>
143
- </box>
144
- </scrollbox>
145
- </>
146
- )}
147
- </Show>
148
- <box flexDirection="row" height={1} flexShrink={0} paddingLeft={4} paddingRight={4} backgroundColor={g().g1} gap={3}>
149
- <text fg={g().g0} bg={g().text}> note </text>
150
- <text fg={g().faint}>{props.base}</text>
151
- <Show when={props.asking()}><text fg={g().growth}>◆ Iso is writing here</text></Show>
152
- <box flexGrow={1} />
153
- <Show when={props.note()?.items.some((i) => i.type === "change")}><text fg={g().faint}>[n] next change</text></Show>
154
- <Show when={!props.note()?.at} fallback={<text fg={g().faint}>[q] back to now</text>}>
155
- <text fg={g().faint}>[t] timeline</text>
156
- <text fg={g().faint}>[/] ask in place</text>
157
- <text fg={g().faint}>[a] its atlas</text>
158
- <text fg={g().faint}>[s] send to agent</text>
159
- <text fg={g().faint}>[b] browser</text>
160
- <text fg={g().faint}>[q] back</text>
161
- </Show>
162
- </box>
163
- </box>
164
- );
165
- }
package/plugin/notes.ts DELETED
@@ -1,10 +0,0 @@
1
- // Which files are notes of the knowledge base: what the plugin hands to isorite rather than to OpenCode.
2
-
3
- import path from "node:path";
4
-
5
- /** Where a file is in the knowledge base, as a note's path, or null when it is not a note of it. */
6
- export function noteOf(notes: string | null, directory: string, file: unknown): string | null {
7
- if (!notes || typeof file !== "string" || !file.toLowerCase().endsWith(".md")) return null;
8
- const rel = path.relative(notes, path.resolve(directory, file));
9
- return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? rel.split(path.sep).join("/") : null;
10
- }
@@ -1,60 +0,0 @@
1
- // A note as the terminal draws it (`Terminal-Note`): its blocks, each marked in the gutter by who wrote most of it
2
- // (your square, an open square for a change made outside isorite, Iso's diamond, an agent's grey one), and the
3
- // changes waiting to be kept, each after the block it belongs to.
4
-
5
- import { authors, blocks, findPassage, locate, pending, type Entry } from "../dist/core.js";
6
-
7
- export type Mark = "you" | "outside" | "iso" | "agent" | "";
8
- export type Kind = "heading" | "math" | "code" | "figure" | "text";
9
-
10
- export interface ViewBlock { type: "block"; kind: Kind; text: string; mark: Mark }
11
- /** A change waiting to be kept: new text (`old` absent), or a passage rewritten. `key` names it to keep or remove. */
12
- export interface ViewChange { type: "change"; key: string; proposal: string; region?: number; who: string; asked: string; old?: string; text: string; placed: boolean }
13
- export type Item = ViewBlock | ViewChange;
14
-
15
- export function markOf(who: string): Mark {
16
- return who === "you" ? "you" : who === "external" ? "outside" : who.startsWith("iso:") ? "iso" : who.startsWith("agent:") ? "agent" : "";
17
- }
18
-
19
- function kindOf(text: string): Kind {
20
- return /^#{1,6}\s/.test(text) ? "heading" : text.startsWith("$$") ? "math" : /^(```|~~~)/.test(text) ? "code" : /^!\[[^\]]*\]\([^)]*\)\s*$/.test(text) ? "figure" : "text";
21
- }
22
-
23
- /** The note's blocks and its waiting changes, in reading order. */
24
- export function noteView(text: string, entries: readonly Entry[]): Item[] {
25
- const all = blocks(text), owners = authors(entries);
26
- // A block's mark: who wrote the most of its lines, the later writer on a tie.
27
- const marked = all.map((b): ViewBlock => {
28
- const counts = new Map<string, number>();
29
- for (let line = b.from; line < b.to; line++) { const who = owners?.[line]; if (who) counts.set(who, (counts.get(who) ?? 0) + 1); }
30
- let best = "", most = 0;
31
- for (const [who, n] of counts) if (n >= most) { best = who; most = n; }
32
- return { type: "block", kind: kindOf(b.text), text: b.text, mark: best ? markOf(best) : "" };
33
- });
34
- // Each change after the block it belongs to; one whose place is gone, at the end.
35
- const after = new Map<number, ViewChange[]>(), end: ViewChange[] = [];
36
- const put = (line: number | null, c: ViewChange) => {
37
- if (line === null) { end.push(c); return; }
38
- let k = -1;
39
- for (let i = 0; i < all.length; i++) if (all[i]!.from <= line) k = i;
40
- after.set(k, [...(after.get(k) ?? []), c]);
41
- };
42
- const lineOf = (char: number) => text.slice(0, char).split("\n").length - 1;
43
- for (const p of pending([...entries])) {
44
- const base = { type: "change" as const, proposal: p.id, who: p.who, asked: p.asked };
45
- if (p.form === "insert") {
46
- const at = locate(text, p);
47
- // An insert goes at a line between blocks: it belongs after the block that ends there.
48
- const line = at?.form === "insert" ? at.line - 1 : null;
49
- put(line, { ...base, key: `p:${p.id}`, text: p.text, placed: line !== null });
50
- continue;
51
- }
52
- for (const k of p.open ?? p.regions.map((_, i) => i)) {
53
- const region = p.regions[k]!, at = findPassage(text, region.old);
54
- put(at ? lineOf(at.from) : null, { ...base, key: `${p.id}:${k}`, region: k, old: region.old.text, text: region.new, placed: at !== null });
55
- }
56
- }
57
- const out: Item[] = [...(after.get(-1) ?? [])];
58
- marked.forEach((b, k) => { out.push(b, ...(after.get(k) ?? [])); });
59
- return [...out, ...end];
60
- }
@@ -1,83 +0,0 @@
1
- // One note's Atlas in terminal cells (`Terminal-Atlas`): what it requires on the left, the note in the middle, what
2
- // builds on it on the right, each a box of two lines of title, joined by routes. The neighbourhood is the core's
3
- // `reach`, as the browser's Atlas lights a chosen note; only the drawing is the terminal's.
4
-
5
- export type Role = "text" | "faint" | "ls" | "req" | "bld" | "gold" | "blank";
6
- export interface Cell { ch: string; role: Role; bold?: boolean }
7
- export interface Box { id: string; title: string; gold?: boolean }
8
-
9
- export const GAP = 5;
10
- const BOX_H = 4, STEP = BOX_H + 1;
11
-
12
- /** A title in two lines of a box's width, cut with an ellipsis past that. */
13
- export function wrapTitle(title: string, width: number): [string, string] {
14
- const words = title.split(/\s+/).filter(Boolean), lines: string[] = [""];
15
- for (const w of words) {
16
- const last = lines[lines.length - 1]!;
17
- if (!last) lines[lines.length - 1] = w;
18
- else if (last.length + 1 + w.length <= width) lines[lines.length - 1] = `${last} ${w}`;
19
- else lines.push(w);
20
- }
21
- const cut = (s: string) => (s.length > width ? s.slice(0, width - 1) + "…" : s);
22
- if (lines.length > 2) return [cut(lines[0]!), cut(lines.slice(1).join(" ")).slice(0, width - 1) + "…"];
23
- return [cut(lines[0] ?? ""), cut(lines[1] ?? "")];
24
- }
25
-
26
- // Box drawing by which sides a cell joins: up, down, left, right.
27
- const JOIN: Record<string, string> = {
28
- "0011": "─", "1100": "│", "0101": "┌", "0110": "┐", "1001": "└", "1010": "┘",
29
- "1101": "├", "1110": "┤", "0111": "┬", "1011": "┴", "1111": "┼", "0001": "─", "0010": "─", "1000": "│", "0100": "│",
30
- };
31
-
32
- /** The grid of cells for a note and its neighbours, `width` cells wide. */
33
- export function drawAtlas(me: Box, requires: Box[], builds: Box[], width: number): Cell[][] {
34
- const col = Math.max(12, Math.min(24, Math.floor((width - 2 * GAP) / 3)));
35
- const rows = Math.max(BOX_H, requires.length * STEP - 1, builds.length * STEP - 1);
36
- const W = 3 * col + 2 * GAP;
37
- const grid: Cell[][] = Array.from({ length: rows }, () => Array.from({ length: W }, (): Cell => ({ ch: " ", role: "blank" })));
38
- const put = (x: number, y: number, ch: string, role: Role, bold = false) => { if (grid[y]?.[x]) grid[y]![x] = { ch, role, ...(bold ? { bold } : {}) }; };
39
-
40
- const box = (x: number, y: number, b: Box, kind: "plain" | "me" | "dashed") => {
41
- const [h, v, c] = kind === "me" ? ["━", "┃", "┏┓┗┛"] : kind === "dashed" ? ["╌", "╎", "┌┐└┘"] : ["─", "│", "┌┐└┘"];
42
- const role: Role = b.gold ? "gold" : kind === "me" ? "text" : "ls";
43
- for (let i = 1; i < col - 1; i++) { put(x + i, y, h, role, kind === "me"); put(x + i, y + BOX_H - 1, h, role, kind === "me"); }
44
- for (let j = 1; j < BOX_H - 1; j++) { put(x, y + j, v, role, kind === "me"); put(x + col - 1, y + j, v, role, kind === "me"); }
45
- put(x, y, c[0]!, role, kind === "me"); put(x + col - 1, y, c[1]!, role, kind === "me");
46
- put(x, y + BOX_H - 1, c[2]!, role, kind === "me"); put(x + col - 1, y + BOX_H - 1, c[3]!, role, kind === "me");
47
- wrapTitle(b.title, col - 4).forEach((line, j) => [...line].forEach((ch, i) => put(x + 2 + i, y + 1 + j, ch, kind === "me" ? "text" : b.gold ? "gold" : "text", kind === "me")));
48
- };
49
-
50
- // A side's routes, in the gap between its boxes (their edge at `boxes`) and the note (its edge at `note`): each
51
- // box's middle row to a trunk in the middle of the gap, and the trunk to the note's middle row.
52
- const routes = (rowsOf: number[], boxes: number, note: number, role: Role) => {
53
- if (!rowsOf.length) return;
54
- const trunk = Math.floor((boxes + note) / 2), at = new Map<string, number[]>();
55
- const join = (x: number, y: number, k: number) => { const j = at.get(`${x},${y}`) ?? [0, 0, 0, 0]; j[k] = 1; at.set(`${x},${y}`, j); };
56
- const across = (y: number, a: number, b: number) => { for (let x = Math.min(a, b); x <= Math.max(a, b); x++) { if (x > Math.min(a, b)) join(x, y, 2); if (x < Math.max(a, b)) join(x, y, 3); } };
57
- const down = (x: number, a: number, b: number) => { for (let y = Math.min(a, b); y <= Math.max(a, b); y++) { if (y > Math.min(a, b)) join(x, y, 0); if (y < Math.max(a, b)) join(x, y, 1); } };
58
- for (const y of rowsOf) { across(y, boxes, trunk); down(trunk, y, mid); }
59
- across(mid, trunk, note);
60
- for (const [key, j] of at) { const [x, y] = key.split(",").map(Number) as [number, number]; put(x, y, JOIN[j.join("")] ?? "┼", role); }
61
- };
62
-
63
- const top = (n: number) => Math.floor((rows - (n * STEP - 1)) / 2);
64
- const meY = Math.floor((rows - BOX_H) / 2), mid = meY + 1;
65
- const leftTop = top(requires.length), rightTop = top(builds.length);
66
- routes(requires.map((_, k) => leftTop + k * STEP + 1), col, col + GAP - 1, "req");
67
- routes(builds.map((_, k) => rightTop + k * STEP + 1), 2 * col + 2 * GAP - 1, 2 * col + GAP, "bld");
68
- requires.forEach((b, k) => box(0, leftTop + k * STEP, b, "plain"));
69
- builds.forEach((b, k) => box(2 * col + 2 * GAP, rightTop + k * STEP, b, "dashed"));
70
- box(col + GAP, meY, me, "me");
71
- return grid;
72
- }
73
-
74
- /** A row of cells as runs of one role, for drawing. */
75
- export function runsOf(row: Cell[]): { text: string; role: Role; bold: boolean }[] {
76
- const out: { text: string; role: Role; bold: boolean }[] = [];
77
- for (const c of row) {
78
- const last = out.at(-1), bold = c.bold ?? false;
79
- if (last && last.role === c.role && last.bold === bold) last.text += c.ch;
80
- else out.push({ text: c.ch, role: c.role, bold });
81
- }
82
- return out;
83
- }
package/plugin/palette.ts DELETED
@@ -1,21 +0,0 @@
1
- // The boards' two grounds (`isorite.css` on the mood board): lapis, dark, and quartz, light.
2
-
3
- export const lapis = {
4
- g0: "#07101C", g1: "#0C1828", g2: "#122238", g3: "#1A3150", line: "#23395A", ls: "#5C7BA6",
5
- text: "#E9EEF5", soft: "#B4C0D2", faint: "#8796AD",
6
- lattice: "#6BA6FF", growth: "#F0BE4A", mal: "#5FD3A4", rose: "#FF8FA6",
7
- } as const;
8
-
9
- export const quartz = {
10
- g0: "#F2F0EA", g1: "#F8F7F3", g2: "#FFFFFF", g3: "#E6E4DC", line: "#D3D0C5", ls: "#7F7D70",
11
- text: "#0E1726", soft: "#3B475B", faint: "#5B677A",
12
- lattice: "#1E4FB8", growth: "#8C6200", mal: "#0B6B4A", rose: "#A81E48",
13
- } as const;
14
-
15
- export type Swatch = keyof typeof lapis;
16
-
17
- /** A ground: a colour for each swatch, lapis's or quartz's. */
18
- export type Ground = Record<Swatch, string>;
19
-
20
- /** The one-note Atlas's routes: what a note requires, and what builds on it (`Terminal-Atlas`). */
21
- export const routeColours = { lapis: { req: "#B79CFF", bld: "#FF9E64" }, quartz: { req: "#6A3FC0", bld: "#B5521B" } } as const;
package/plugin/play.ts DELETED
@@ -1,104 +0,0 @@
1
- // The notes in play in a session: what the agent's look-ups found, what it reached along links from them, and how
2
- // far each got (found and not opened, looked at, read, or used in an answer). Drawn as a tree, as `Terminal-Chat`
3
- // draws the notes found, in the sidebar.
4
-
5
- /** One step, as the isorite server publishes it (the engine's Step). */
6
- export interface Step {
7
- tool: string;
8
- how: "keyword" | "meaning" | "link" | "read";
9
- notes: string[];
10
- opened?: string;
11
- from?: string;
12
- rel?: string;
13
- query?: string;
14
- scores?: Record<string, number>;
15
- failed?: boolean;
16
- }
17
-
18
- export type State = "not opened" | "looked at" | "read" | "used";
19
- const RANK: Record<State, number> = { "not opened": 0, "looked at": 1, read: 2, used: 3 };
20
-
21
- export interface InPlay {
22
- id: string;
23
- how: Step["how"];
24
- /** How it was found: the words searched for, or how alike in meaning. */
25
- query?: string;
26
- score?: number;
27
- /** For a note reached along a link: the note it was reached from, and what the link says. */
28
- from?: string;
29
- rel?: string;
30
- state: State;
31
- }
32
-
33
- export interface Row {
34
- /** The tree's lines before it: "├─ ", "│ └─ ". */
35
- prefix: string;
36
- id: string;
37
- /** What it is called, and how it came: `keyword "mixing"`, `meaning 0.81`, `requires ›`. */
38
- label: string;
39
- detail: string;
40
- state: State;
41
- }
42
-
43
- export class Play {
44
- private readonly notes = new Map<string, InPlay>();
45
-
46
- /** Takes in one of the agent's steps. */
47
- add(step: Step): void {
48
- if (step.failed) return;
49
- if (step.tool === "search_notes" || step.tool === "find_similar") {
50
- for (const id of step.notes) {
51
- if (this.notes.has(id)) continue;
52
- const score = step.scores?.[id];
53
- this.notes.set(id, { id, how: step.how, ...(step.query ? { query: step.query } : {}), ...(score === undefined ? {} : { score }), state: "not opened" });
54
- }
55
- return;
56
- }
57
- if (!step.opened) return;
58
- const known = this.notes.get(step.opened);
59
- const reached: InPlay = known ?? { id: step.opened, how: step.how, ...(step.from ? { from: step.from } : {}), ...(step.rel ? { rel: step.rel } : {}), state: "not opened" };
60
- this.notes.set(step.opened, { ...reached, state: this.higher(reached.state, step.tool === "outline_note" ? "looked at" : "read") });
61
- }
62
-
63
- /** The notes an answer cites are used. Whether any was not used before. */
64
- cite(ids: Iterable<string>): boolean {
65
- let changed = false;
66
- for (const id of ids) {
67
- const known = this.notes.get(id);
68
- if (known && known.state !== "used") { this.notes.set(id, { ...known, state: "used" }); changed = true; }
69
- }
70
- return changed;
71
- }
72
-
73
- get size(): number { return this.notes.size; }
74
-
75
- /** How many were found by searching, how many more by following links, how many used. */
76
- summary(): { found: number; followed: number; used: number } {
77
- const all = [...this.notes.values()];
78
- return { found: all.filter((n) => !n.from).length, followed: all.filter((n) => n.from).length, used: all.filter((n) => n.state === "used").length };
79
- }
80
-
81
- /** The tree, a row a note: those found at the top in the order found, each with what was reached from it beneath. */
82
- rows(title: (id: string) => string): Row[] {
83
- const all = [...this.notes.values()];
84
- const under = (parent: string | undefined) => all.filter((n) => (n.from && this.notes.has(n.from) ? n.from : undefined) === parent);
85
- const out: Row[] = [];
86
- const walk = (parent: string | undefined, lead: string) => {
87
- const kids = under(parent);
88
- kids.forEach((n, k) => {
89
- const last = k === kids.length - 1;
90
- const named = title(n.id).toLowerCase();
91
- out.push({
92
- prefix: lead + (last ? "└─ " : "├─ "), id: n.id, state: n.state,
93
- label: n.from ? `${n.rel ?? "links to"} › ${named}` : named,
94
- detail: n.from ? "" : n.how === "meaning" ? `meaning${n.score === undefined ? "" : ` ${n.score.toFixed(2)}`}` : n.how === "keyword" ? `keyword${n.query ? ` "${n.query}"` : ""}` : "",
95
- });
96
- walk(n.id, lead + (last ? " " : "│ "));
97
- });
98
- };
99
- walk(undefined, "");
100
- return out;
101
- }
102
-
103
- private higher(a: State, b: State): State { return RANK[b] > RANK[a] ? b : a; }
104
- }