pi-weave 0.1.12 → 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 +8 -37
- package/package.json +1 -2
- package/src/core/concurrency.ts +3 -6
- package/src/core/frontmatter.ts +0 -53
- package/src/core/graph/build.ts +6 -7
- package/src/core/graph/current.ts +2 -4
- package/src/core/graph/model.ts +1 -1
- package/src/core/graph/wikilinks.ts +3 -3
- package/src/core/index.ts +26 -27
- package/src/core/paths.ts +0 -7
- package/src/core/vault.ts +16 -681
- package/src/core/view/detail.ts +1 -1
- package/src/core/view/health.ts +1 -1
- package/src/core/view/tree.ts +1 -1
- package/src/pi/index.ts +6 -85
- package/src/pi/summarize.ts +2 -2
- package/src/pi/viewer/tui/bodyStore.ts +4 -7
- package/src/pi/viewer/tui/branding.ts +7 -148
- package/src/pi/viewer/tui/run.ts +3 -17
- package/src/pi/viewer/tui/surface/base.ts +24 -3
- package/src/pi/viewer/tui/surface/explore.ts +41 -6
- package/src/pi/viewer/tui/workspace.ts +23 -351
- package/src/pi/viewer/tui/workspaceRoot.ts +31 -172
- package/src/pi/viewer/web/run.ts +7 -117
- package/src/web/client/api.dom.ts +2 -2
- package/src/web/client/api.ts +14 -223
- package/src/web/client/bootstrap.ts +5 -14
- package/src/web/client/context/context.model.ts +9 -11
- package/src/web/client/dist/app.js +93 -219
- package/src/web/client/graph/dynamics.ts +5 -65
- package/src/web/client/graph/renderer.dom.ts +7 -8
- package/src/web/client/graph/renderer.ts +9 -35
- package/src/web/client/main.tsx +1 -1
- package/src/web/client/note/Note.tsx +21 -63
- package/src/web/client/search/SearchPalette.tsx +45 -36
- package/src/web/client/search/search.model.ts +33 -454
- package/src/web/client/shell/Columns.tsx +13 -83
- package/src/web/client/shell/Header.tsx +2 -10
- package/src/web/client/shell/Shell.tsx +50 -125
- package/src/web/client/shell/StatusBar.tsx +1 -4
- package/src/web/client/shell/icons.model.ts +4 -7
- package/src/web/client/shell/keys.model.ts +5 -42
- package/src/web/client/shell/keys.ts +2 -2
- package/src/web/client/shell/shell.model.ts +10 -133
- package/src/web/client/shell/theme.model.ts +2 -2
- package/src/web/client/shell/theme.ts +33 -157
- package/src/web/client/state.ts +9 -89
- package/src/web/client/tree/Tree.tsx +25 -575
- package/src/web/client/tree/tree.model.ts +8 -162
- package/src/web/client/workspace.ts +72 -242
- package/src/web/server/page.ts +8 -10
- package/src/web/server/routes.ts +30 -563
- package/src/web/server/server.ts +6 -145
- package/src/web/shared/layout.ts +72 -624
- package/src/web/shared/wire.ts +10 -196
- package/src/core/sessions.ts +0 -929
- package/src/pi/sessionScan.ts +0 -104
- package/src/pi/viewer/tui/explorer.ts +0 -586
- package/src/web/client/live.model.ts +0 -275
- package/src/web/client/live.ts +0 -151
- package/src/web/client/note/Editor.tsx +0 -109
- package/src/web/client/note/editor.controller.ts +0 -151
- package/src/web/client/note/editor.model.ts +0 -686
- package/src/web/client/search/search.ts +0 -107
- package/src/web/client/shell/Divider.tsx +0 -44
- package/src/web/client/shell/cssvars.ts +0 -70
- package/src/web/client/shell/drag.model.ts +0 -170
- package/src/web/client/shell/layout.model.ts +0 -500
- package/src/web/client/shell/viewport.ts +0 -29
- package/src/web/server/sse.ts +0 -321
- package/src/web/server/watcher.ts +0 -507
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* `page.ts` emits a nonce'd `<style>` block, but it is deliberately tiny —
|
|
8
8
|
* the custom-property palette and enough body rules that the first paint has
|
|
9
|
-
* a background colour before `app.js` parses. It knows nothing about
|
|
10
|
-
*
|
|
9
|
+
* a background colour before `app.js` parses. It knows nothing about the
|
|
10
|
+
* status bar, and it is owned by the server tier, which this
|
|
11
11
|
* work may not edit. There is also no `/app.css` route. So the shell's own
|
|
12
12
|
* rules have to travel in the bundle and be installed at runtime.
|
|
13
13
|
*
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* 1. **A literal `style="…"` attribute in markup is blocked.** That is what
|
|
20
20
|
* `'unsafe-inline'` governs for styles, and it is absent.
|
|
21
21
|
* 2. **CSSOM writes are not.** `el.style.setProperty(…)` has no CSP hook at
|
|
22
|
-
* all, which is why
|
|
23
|
-
*
|
|
22
|
+
* all, which is why dynamic note preview positions use CSSOM. Confirmed
|
|
23
|
+
* against Preact's own behaviour:
|
|
24
24
|
* `preact/src/diff/props.js` handles a `style` prop via
|
|
25
25
|
* `dom.style.cssText` or `style.setProperty`, never `setAttribute`.
|
|
26
26
|
* 3. **A script-created `<style>` element needs the nonce.** Inserting a
|
|
@@ -161,7 +161,7 @@ export const THEME_CSS = `
|
|
|
161
161
|
ui 11.5 status bar, the note's meta row, small controls
|
|
162
162
|
row 12 list rows, text inputs, inline code
|
|
163
163
|
base 13 body copy, h3-h6, preview-card text
|
|
164
|
-
body 13.5 the note column's prose
|
|
164
|
+
body 13.5 the note column's prose
|
|
165
165
|
subhead 14 palette input, note-body h2, header icon buttons
|
|
166
166
|
title 15 note-body h1
|
|
167
167
|
display 20 the note's page title
|
|
@@ -244,30 +244,21 @@ body{font-size:var(--weave-px-base)}
|
|
|
244
244
|
}
|
|
245
245
|
.weave-theme:hover{color:var(--weave-fg);background:var(--weave-line)}
|
|
246
246
|
|
|
247
|
-
/* connection indicator -------------------------------------------------- */
|
|
248
|
-
.weave-conn{display:inline-flex;align-items:center;gap:5px;font-size:var(--weave-px-ui);white-space:nowrap}
|
|
249
|
-
.weave-conn-dot{font-size:var(--weave-px-prov);line-height:1}
|
|
250
|
-
/* Reconnecting breathes instead of sitting there: the one state whose whole
|
|
251
|
-
message is "hold on, I am still working" is the one that should keep
|
|
252
|
-
saying it. The offline dot stays still — its message needs a decision,
|
|
253
|
-
not patience. The global reduced-motion kill switch restores the static
|
|
254
|
-
dot, the same way it does every animation in this sheet. */
|
|
255
|
-
.weave-conn-ok .weave-conn-dot,.weave-conn-ok{color:var(--weave-ok)}
|
|
256
|
-
.weave-conn-warn .weave-conn-dot,.weave-conn-warn{color:var(--weave-warn)}
|
|
257
|
-
.weave-conn-warn .weave-conn-dot{animation:weave-conn-pulse 1.6s ease-in-out infinite}
|
|
258
|
-
.weave-conn-bad .weave-conn-dot,.weave-conn-bad{color:var(--weave-bad)}
|
|
259
|
-
@keyframes weave-conn-pulse{0%,100%{opacity:1}50%{opacity:.35}}
|
|
260
|
-
|
|
261
247
|
/* the grid -------------------------------------------------------------- */
|
|
262
|
-
/*
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
.weave-grid
|
|
270
|
-
.weave-
|
|
248
|
+
/* One boring grid. CSS owns the breakpoints; no saved widths, divider state
|
|
249
|
+
or viewport listener can make the shell disagree with the browser. */
|
|
250
|
+
.weave-grid{
|
|
251
|
+
display:grid;min-height:0;overflow:hidden;background:var(--weave-line);
|
|
252
|
+
grid-template-columns:minmax(180px,22fr) minmax(320px,46fr) minmax(260px,32fr);
|
|
253
|
+
}
|
|
254
|
+
@media (max-width:1099px){
|
|
255
|
+
.weave-grid{grid-template-columns:minmax(180px,32fr) minmax(320px,68fr)}
|
|
256
|
+
.weave-col-graph{display:none}
|
|
257
|
+
}
|
|
258
|
+
@media (max-width:799px){
|
|
259
|
+
.weave-grid{grid-template-columns:1fr}
|
|
260
|
+
.weave-col-tree{display:none}
|
|
261
|
+
}
|
|
271
262
|
|
|
272
263
|
.weave-col{
|
|
273
264
|
display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;
|
|
@@ -286,18 +277,8 @@ body{font-size:var(--weave-px-base)}
|
|
|
286
277
|
inside the same region instead of squeezing the graph. */
|
|
287
278
|
.weave-col-graph{display:grid;grid-template-rows:auto minmax(0,1fr) minmax(96px,40%);background:var(--weave-bg)}
|
|
288
279
|
|
|
289
|
-
/* dividers -------------------------------------------------------------- */
|
|
290
|
-
.weave-divider{
|
|
291
|
-
background:var(--weave-line);cursor:col-resize;position:relative;
|
|
292
|
-
touch-action:none;
|
|
293
|
-
}
|
|
294
|
-
/* A 1 px target is unhittable, so the hit area is widened with a pseudo
|
|
295
|
-
element rather than by making the visible rule thicker. */
|
|
296
|
-
.weave-divider::after{content:"";position:absolute;inset:0 -3px;z-index:1}
|
|
297
|
-
.weave-divider:hover,.weave-divider:focus-visible{background:var(--weave-accent);outline:none}
|
|
298
|
-
|
|
299
280
|
/* tree column ----------------------------------------------------------- */
|
|
300
|
-
/* --weave-depth is written per row by \`depthVar\`, through
|
|
281
|
+
/* --weave-depth is written per row by \`depthVar\`, through CSSOM
|
|
301
282
|
path as the column widths, and multiplied by a step this sheet owns — so
|
|
302
283
|
the tree's density stays a CSS decision. */
|
|
303
284
|
.weave-tree{display:flex;flex-direction:column;min-height:0;flex:1}
|
|
@@ -316,8 +297,6 @@ body{font-size:var(--weave-px-base)}
|
|
|
316
297
|
border:1px solid var(--weave-line-strong);border-radius:var(--weave-radius);
|
|
317
298
|
}
|
|
318
299
|
.weave-chip:hover{color:var(--weave-fg);border-color:var(--weave-accent)}
|
|
319
|
-
.weave-chip-bad{color:var(--weave-bad);border-color:var(--weave-bad)}
|
|
320
|
-
.weave-chip-bad:hover{color:var(--weave-fg);background:var(--weave-bad);border-color:var(--weave-bad)}
|
|
321
300
|
.weave-rows{
|
|
322
301
|
flex:1;min-height:0;overflow:auto;margin:0;padding:3px 0;list-style:none;
|
|
323
302
|
}
|
|
@@ -329,45 +308,22 @@ body{font-size:var(--weave-px-base)}
|
|
|
329
308
|
cursor:default;white-space:nowrap;
|
|
330
309
|
}
|
|
331
310
|
.weave-row:hover{background:var(--weave-line)}
|
|
332
|
-
.weave-row.weave-row-droptarget{
|
|
333
|
-
background:var(--weave-new);outline:1px dashed var(--weave-accent);outline-offset:-1px;
|
|
334
|
-
}
|
|
335
|
-
.weave-tree-new-folder{
|
|
336
|
-
display:flex;align-items:center;gap:6px;padding:4px var(--weave-gutter);
|
|
337
|
-
border-bottom:1px solid var(--weave-line);background:var(--weave-panel);
|
|
338
|
-
}
|
|
339
|
-
.weave-row-del{
|
|
340
|
-
display:none;margin-left:auto;padding:0 4px;font:inherit;font-size:var(--weave-px-row);
|
|
341
|
-
line-height:1;color:var(--weave-faint);background:none;border:0;cursor:pointer;
|
|
342
|
-
border-radius:var(--weave-radius);
|
|
343
|
-
}
|
|
344
|
-
.weave-row:hover .weave-row-del{display:inline-flex;align-items:center}
|
|
345
|
-
.weave-row-del:hover{color:var(--weave-bad);background:var(--weave-panel)}
|
|
346
311
|
/* A hover changes two things, in this order: the ground appears under the
|
|
347
312
|
row, then the label steps up to fg. The second half is what stops the
|
|
348
313
|
hover from reading as a stray grey rectangle — the row answers the pointer
|
|
349
314
|
in both channels at once. */
|
|
350
315
|
.weave-row:hover .weave-label{color:var(--weave-fg)}
|
|
351
|
-
/* The session fold (tree.model.ts's isMuted): machine-written memory that
|
|
352
|
-
accrues by the dozens with near-duplicate titles, dropped one notch so the
|
|
353
|
-
rows a human wrote keep the foreground. --weave-dim holds ≥ 4.5 on both
|
|
354
|
-
grounds, so this is quiet, not illegible — and the two states below always
|
|
355
|
-
restore full weight, which is why the model never emits both classes on
|
|
356
|
-
one row. */
|
|
357
|
-
.weave-row-muted .weave-label{color:var(--weave-dim)}
|
|
358
|
-
.weave-row-muted:hover .weave-label{color:var(--weave-fg)}
|
|
359
316
|
/* The selection has one voice. On the tinted ground every quieter token still
|
|
360
317
|
fails contrast — even --weave-dim lands under 4.5 — so a selected row's
|
|
361
318
|
kind, provenance and meta children join its label in --weave-fg. The
|
|
362
319
|
provenance glyph shape carries the distinction the colour swap drops;
|
|
363
320
|
elsewhere the hues are unaffected. The palette's hit rows need the same
|
|
364
321
|
remap: same selected ground, same failure. The label is restated last
|
|
365
|
-
|
|
366
|
-
on. */
|
|
322
|
+
so the selected row is always readable. */
|
|
367
323
|
.weave-row-on .weave-twisty,.weave-row-on .weave-kind,.weave-row-on .weave-prov,
|
|
368
324
|
.weave-row-on .weave-meta,.weave-row-on .weave-label{color:var(--weave-fg)}
|
|
369
325
|
.weave-hit-on .weave-hit-badge,.weave-hit-on .weave-hit-detail{color:var(--weave-fg)}
|
|
370
|
-
/* A newly-arrived node (a file or note added since the last update,
|
|
326
|
+
/* A newly-arrived node (a file or note added since the last update, ):
|
|
371
327
|
one short highlight that fades while the label settles from bold back to
|
|
372
328
|
normal. The class is computed from the frame diff in workspace.ts and
|
|
373
329
|
expires with it, so collapsing and re-expanding later does not replay the
|
|
@@ -394,7 +350,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
394
350
|
|
|
395
351
|
/* icons ------------------------------------------------------------------
|
|
396
352
|
The sprite's only CSS: one colour (the glyph inherits the row's, so a
|
|
397
|
-
|
|
353
|
+
quiet row's icon recedes with it and a selected row's brightens with it)
|
|
398
354
|
and the twisty's rotation, which is the *same* 16px chevron the rail's
|
|
399
355
|
group headings use — right-pointing closed, rotated 90° open. The
|
|
400
356
|
rotation is not in the motion block's transition list, on purpose: that
|
|
@@ -410,8 +366,8 @@ body{font-size:var(--weave-px-base)}
|
|
|
410
366
|
so it lies on --weave-page — one step off the desk and the canvas grounds.
|
|
411
367
|
The 2px rule flush to its left edge is the spine: it takes the note's
|
|
412
368
|
provenance colour (see the --weave-spine map), so the document's origin is
|
|
413
|
-
readable peripherally, before any glyph is. Head
|
|
414
|
-
--weave-note-gutter so
|
|
369
|
+
readable peripherally, before any glyph is. Head and body share the
|
|
370
|
+
--weave-note-gutter so the page has one consistent measure. */
|
|
415
371
|
.weave-note{display:flex;flex-direction:column;min-height:0;flex:1;overflow:auto;background:var(--weave-page)}
|
|
416
372
|
.weave-note-human{--weave-spine:var(--weave-ok)}
|
|
417
373
|
.weave-note-agent{--weave-spine:var(--weave-accent)}
|
|
@@ -423,9 +379,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
423
379
|
alternative, a title that scrolls away, is how a reader ends up annotating
|
|
424
380
|
the wrong file. It carries the page ground rather than a fill or a shadow,
|
|
425
381
|
so the reveal reads as the page continuing under it and not as a bar
|
|
426
|
-
arriving; the hairline is the only seam. The
|
|
427
|
-
sibling in the column flow, so it can never slide underneath — no stacking
|
|
428
|
-
contest exists between them. The head's z-index is only what keeps it above
|
|
382
|
+
arriving; the hairline is the only seam. The head's z-index is only what keeps it above
|
|
429
383
|
the prose (and the wikilinks inside it) as that prose scrolls beneath. */
|
|
430
384
|
.weave-note-head{position:sticky;top:0;z-index:2;padding:14px var(--weave-note-gutter) 9px;background:var(--weave-page);border-bottom:1px solid var(--weave-line)}
|
|
431
385
|
/* The page's largest voice, and the point of the review's headline finding:
|
|
@@ -439,11 +393,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
439
393
|
so the eye can read the whole of it without ever reading it. */
|
|
440
394
|
.weave-note-meta{margin:0;display:flex;align-items:center;gap:8px;font-size:var(--weave-px-ui);color:var(--weave-dim);flex-wrap:wrap}
|
|
441
395
|
.weave-note-time{color:var(--weave-faint)}
|
|
442
|
-
/* \`Open in $EDITOR
|
|
443
|
-
bordered button — the note's loudest control, sitting between its title and
|
|
444
|
-
its prose. Now it is an icon at the end of the meta line: the hint rides
|
|
445
|
-
the \`title\`/\`aria-label\` (\`editor.model.ts\`'s OPEN_HINT/OPEN_LABEL), and
|
|
446
|
-
the control is quiet until the pointer asks for it. */
|
|
396
|
+
/* \`Open in $EDITOR\` is a quiet icon at the end of the meta line. */
|
|
447
397
|
.weave-note-open{
|
|
448
398
|
display:inline-flex;align-items:center;justify-content:center;
|
|
449
399
|
width:22px;height:22px;padding:0;font:inherit;color:var(--weave-faint);
|
|
@@ -460,9 +410,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
460
410
|
withdrawn at the user's call: this is a workspace, and the note shares the
|
|
461
411
|
width the desk gives it. It reads at --weave-px-body on 1.7 rather than the
|
|
462
412
|
chrome's 1.6: the extra leading is what a page of running text needs that a
|
|
463
|
-
status bar does not. Code
|
|
464
|
-
reading prose and reading raw are different postures, and the face switch
|
|
465
|
-
is the toggle. */
|
|
413
|
+
status bar does not. Code stays mono deliberately. */
|
|
466
414
|
.weave-note-body{padding:12px var(--weave-note-gutter) 28px;font-size:var(--weave-px-body);line-height:1.7;color:var(--weave-fg)}
|
|
467
415
|
.weave-note-body>*:first-child{margin-top:0}
|
|
468
416
|
.weave-note-body h1,.weave-note-body h2,.weave-note-body h3,
|
|
@@ -550,53 +498,6 @@ body{font-size:var(--weave-px-base)}
|
|
|
550
498
|
underlines a hover should not land with a thud. */
|
|
551
499
|
@keyframes weave-preview-in{from{opacity:0;transform:translateY(2px)}}
|
|
552
500
|
|
|
553
|
-
/* note editor (§11 P5) ---------------------------------------------------- */
|
|
554
|
-
/* A \`<textarea>\`, not CodeMirror: §0 V10 measured CM6 at 118 KB gzip, more
|
|
555
|
-
than the entire rest of the client. It inherits the body's type so toggling
|
|
556
|
-
\`⌘E\` does not reflow the column into a different shape. */
|
|
557
|
-
.weave-note-bar{
|
|
558
|
-
display:flex;align-items:center;gap:6px;flex-wrap:wrap;
|
|
559
|
-
padding:5px var(--weave-gutter);border-bottom:1px solid var(--weave-line);
|
|
560
|
-
}
|
|
561
|
-
/* \`weave-note-open\` is styled in the note column above (P6.3 moved it into
|
|
562
|
-
the head's meta row as an icon), and no longer shares the bar's bordered
|
|
563
|
-
button voice. */
|
|
564
|
-
.weave-note-toggle,.weave-note-save,.weave-note-action{
|
|
565
|
-
font:inherit;font-size:var(--weave-px-ui);padding:2px 8px;cursor:pointer;
|
|
566
|
-
color:var(--weave-fg);background:var(--weave-raise);
|
|
567
|
-
border:1px solid var(--weave-line);border-radius:var(--weave-radius);
|
|
568
|
-
}
|
|
569
|
-
.weave-note-toggle:hover,.weave-note-save:hover,.weave-note-action:hover{
|
|
570
|
-
border-color:var(--weave-accent);
|
|
571
|
-
}
|
|
572
|
-
.weave-note-toggle[aria-pressed="true"]{border-color:var(--weave-accent);color:var(--weave-accent)}
|
|
573
|
-
.weave-note-save:disabled{opacity:.45;cursor:default;border-color:var(--weave-line)}
|
|
574
|
-
.weave-note-dirty{color:var(--weave-accent);font-size:var(--weave-px-subhead);line-height:1}
|
|
575
|
-
.weave-note-status{font-size:var(--weave-px-ui);color:var(--weave-dim)}
|
|
576
|
-
.weave-note-status-ok{color:var(--weave-ok)}
|
|
577
|
-
.weave-note-status-warn{color:var(--weave-warn)}
|
|
578
|
-
/* The prompt takes the full row rather than sitting inline: reload-or-overwrite
|
|
579
|
-
is a decision about the user's unsaved text, and a decision squeezed between
|
|
580
|
-
two buttons reads as a hint. */
|
|
581
|
-
.weave-note-prompt{
|
|
582
|
-
flex-basis:100%;display:flex;align-items:center;gap:8px;flex-wrap:wrap;
|
|
583
|
-
margin-top:4px;padding:5px 8px;border-radius:var(--weave-radius);
|
|
584
|
-
background:var(--weave-raise);border:1px solid var(--weave-warn);
|
|
585
|
-
}
|
|
586
|
-
.weave-note-prompt-conflict,.weave-note-prompt-collision{border-color:var(--weave-warn)}
|
|
587
|
-
.weave-note-prompt-external,.weave-note-prompt-discard{border-color:var(--weave-line)}
|
|
588
|
-
.weave-note-prompt-text{margin:0;font-size:var(--weave-px-ui);color:var(--weave-fg)}
|
|
589
|
-
.weave-note-prompt-actions{margin:0;display:flex;gap:6px;flex-wrap:wrap}
|
|
590
|
-
.weave-note-editor{
|
|
591
|
-
flex:1;min-height:240px;resize:none;
|
|
592
|
-
padding:10px var(--weave-note-gutter) 24px;
|
|
593
|
-
/* Exactly the prose body's size and leading. The invariant is documented
|
|
594
|
-
and deliberate: ⌘E must not reflow, and reading raw at the same measure
|
|
595
|
-
the prose uses is what makes the toggle a lens rather than a jump. */
|
|
596
|
-
font:inherit;font-family:var(--weave-mono);font-size:var(--weave-px-body);line-height:1.7;
|
|
597
|
-
color:var(--weave-fg);background:var(--weave-page);border:0;outline-offset:-2px;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
501
|
/* graph column ---------------------------------------------------------- */
|
|
601
502
|
/* The canvas is a plain block sigma appends its own <canvas> layers into. It
|
|
602
503
|
must have a definite size before sigma measures it, hence \`min-height\` and
|
|
@@ -707,14 +608,14 @@ body{font-size:var(--weave-px-base)}
|
|
|
707
608
|
/* status bar ------------------------------------------------------------ */
|
|
708
609
|
/* One separator vocabulary, shared with the header summary: every run of
|
|
709
610
|
status items after the first opens with the same mid-dot, in the same
|
|
710
|
-
--weave-faint. cwd · selection
|
|
611
|
+
--weave-faint. cwd · selection · stamp reads as one grammar,
|
|
711
612
|
the same way the header's three-part summary does. */
|
|
712
613
|
.weave-status{
|
|
713
614
|
display:flex;align-items:center;gap:8px;height:22px;padding:0 var(--weave-gutter);
|
|
714
615
|
font-family:var(--weave-mono);font-size:var(--weave-px-ui);color:var(--weave-dim);
|
|
715
616
|
border-top:1px solid var(--weave-line);background:var(--weave-panel);
|
|
716
617
|
}
|
|
717
|
-
.weave-status-sel::before
|
|
618
|
+
.weave-status-sel::before{content:"·";margin-right:8px;color:var(--weave-faint)}
|
|
718
619
|
.weave-status-cwd,.weave-status-sel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
719
620
|
.weave-status-cwd{max-width:38%;color:var(--weave-faint)}
|
|
720
621
|
.weave-status-sel{color:var(--weave-fg)}
|
|
@@ -737,31 +638,11 @@ body{font-size:var(--weave-px-base)}
|
|
|
737
638
|
@media (prefers-color-scheme: light){
|
|
738
639
|
:root:not([data-weave-theme="dark"]) .weave-scrim{background:rgba(76,79,105,.30)}
|
|
739
640
|
}
|
|
740
|
-
.weave-palette,.weave-help
|
|
641
|
+
.weave-palette,.weave-help{
|
|
741
642
|
display:flex;flex-direction:column;width:100%;max-width:560px;max-height:72vh;
|
|
742
643
|
overflow:hidden;background:var(--weave-panel);color:var(--weave-fg);
|
|
743
644
|
border:1px solid var(--weave-line-strong);border-radius:var(--weave-radius-pop);
|
|
744
645
|
}
|
|
745
|
-
.weave-dialog{max-width:380px;padding:16px;overflow:visible}
|
|
746
|
-
.weave-dialog-title{margin:0 0 8px;font-size:var(--weave-px-subhead);font-weight:600;color:var(--weave-fg)}
|
|
747
|
-
.weave-dialog-body{margin:0 0 16px;font-size:var(--weave-px-row);line-height:1.5;color:var(--weave-dim)}
|
|
748
|
-
.weave-dialog-input{width:100%;margin-bottom:12px;box-sizing:border-box}
|
|
749
|
-
.weave-dialog-actions{display:flex;justify-content:flex-end;gap:8px}
|
|
750
|
-
.weave-menu-backdrop{position:fixed;inset:0;z-index:9}
|
|
751
|
-
.weave-menu{
|
|
752
|
-
position:fixed;left:var(--weave-menu-x,0);top:var(--weave-menu-y,0);z-index:10;
|
|
753
|
-
min-width:160px;padding:4px 0;background:var(--weave-panel);
|
|
754
|
-
border:1px solid var(--weave-line-strong);border-radius:var(--weave-radius-pop);
|
|
755
|
-
}
|
|
756
|
-
.weave-menu-item{
|
|
757
|
-
display:flex;align-items:center;gap:8px;width:100%;height:26px;padding:0 10px;
|
|
758
|
-
font:inherit;font-size:var(--weave-px-row);text-align:left;color:var(--weave-fg);
|
|
759
|
-
background:none;border:0;cursor:pointer;white-space:nowrap;
|
|
760
|
-
}
|
|
761
|
-
.weave-menu-item:hover{background:var(--weave-line)}
|
|
762
|
-
.weave-menu-item-bad{color:var(--weave-bad)}
|
|
763
|
-
.weave-menu-item-bad:hover{color:var(--weave-bad);background:var(--weave-line)}
|
|
764
|
-
.weave-menu-sep{height:1px;margin:4px 0;background:var(--weave-line);border:0}
|
|
765
646
|
.weave-palette-input{
|
|
766
647
|
flex:none;height:34px;padding:0 11px;font:inherit;font-size:var(--weave-px-subhead);
|
|
767
648
|
color:var(--weave-fg);background:transparent;border:0;
|
|
@@ -803,7 +684,7 @@ body{font-size:var(--weave-px-base)}
|
|
|
803
684
|
--weave-new tint is exactly an accent at a strength text stays legible in. */
|
|
804
685
|
::selection{background:var(--weave-new)}
|
|
805
686
|
/* Every scroller in the workspace (rows, note body, rail, palette results,
|
|
806
|
-
help sheet
|
|
687
|
+
help sheet) gets the same thin chrome: the default scrollbar is a
|
|
807
688
|
15 px system object the hairline aesthetic cannot afford, and Firefox and
|
|
808
689
|
the Blink/WebKit pair cover the whole surface with these five rules. */
|
|
809
690
|
*{scrollbar-width:thin;scrollbar-color:var(--weave-line-strong) transparent}
|
|
@@ -823,18 +704,13 @@ body{font-size:var(--weave-px-base)}
|
|
|
823
704
|
properties only — colour, border, opacity, transform; height and padding
|
|
824
705
|
snap. */
|
|
825
706
|
.weave-row,.weave-ctx-link,.weave-chip,.weave-search,.weave-refresh,.weave-theme,
|
|
826
|
-
.weave-
|
|
827
|
-
.weave-note-action{
|
|
707
|
+
.weave-hit,.weave-note-open{
|
|
828
708
|
transition:background-color 120ms ease,border-color 120ms ease,color 120ms ease;
|
|
829
709
|
}
|
|
830
710
|
.weave-scrim{animation:weave-fade-in 140ms ease-out both}
|
|
831
711
|
.weave-palette,.weave-help{animation:weave-overlay-in 160ms ease-out both}
|
|
832
|
-
/* The conflict prompt mounts once per conflict, so its entrance cannot strobe
|
|
833
|
-
the way a context-rail flash would — that rail deliberately stays still. */
|
|
834
|
-
.weave-note-prompt{animation:weave-prompt-in 180ms ease-out both}
|
|
835
712
|
@keyframes weave-fade-in{from{opacity:0}}
|
|
836
713
|
@keyframes weave-overlay-in{from{opacity:0;transform:translateY(4px)}}
|
|
837
|
-
@keyframes weave-prompt-in{from{opacity:0;transform:translateY(-2px)}}
|
|
838
714
|
@media (prefers-reduced-motion: reduce){*{transition:none!important;animation:none!important}}
|
|
839
715
|
`;
|
|
840
716
|
|
package/src/web/client/state.ts
CHANGED
|
@@ -1,100 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The context bus (weave-workspace §1.3).
|
|
3
|
-
*
|
|
4
|
-
* No event bus, no pub/sub — five Preact signals. Selecting in the tree,
|
|
5
|
-
* clicking a graph node, following a wikilink and hitting a search result all
|
|
6
|
-
* write {@link selectedId}; the note column, the graph highlight and the
|
|
7
|
-
* context rail are computed from it.
|
|
8
|
-
*
|
|
9
|
-
* ## Tier rules (§2)
|
|
10
|
-
*
|
|
11
|
-
* This is `src/web/client/**`: it may import `src/web/shared` and browser
|
|
12
|
-
* deps, and must **never** import `src/core` or `node:*`. Core is
|
|
13
|
-
* Node-flavoured TypeScript that would drag `node:fs` into the bundle.
|
|
14
|
-
*
|
|
15
|
-
* ## What the P0 placeholders became
|
|
16
|
-
*
|
|
17
|
-
* This file used to declare three local interfaces behind `TODO(P1)` markers,
|
|
18
|
-
* standing in for contracts that had not been written yet. Two of them are
|
|
19
|
-
* now real and imported from `src/web/shared/wire.ts` — {@link graph} carries
|
|
20
|
-
* a `GraphPayload` and {@link noteBody} a `ViewNote`, the exact values
|
|
21
|
-
* `src/web/client/api.ts` returns. That removes a translation step that
|
|
22
|
-
* existed only to satisfy a stale type: the fetchers already produce these
|
|
23
|
-
* shapes, so anything narrower here was a downcast waiting to lose a field.
|
|
24
|
-
*
|
|
25
|
-
* The third resolved the other way. The TODO said to take `TreeState` from
|
|
26
|
-
* the wire too, and that was simply wrong: which rows a user has expanded is
|
|
27
|
-
* **not** a wire contract. It never crosses the network, the server has no
|
|
28
|
-
* opinion about it, and putting it in `wire.ts` would have made the
|
|
29
|
-
* client/server contract carry a purely local UI preference. So
|
|
30
|
-
* {@link TreeState} stays here, declared where it is owned, with the marker
|
|
31
|
-
* removed rather than carried forward.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { signal } from "@preact/signals";
|
|
35
1
|
import type { GraphPayload, NotePayload } from "../shared/wire";
|
|
36
2
|
|
|
37
|
-
/** Connection state shown in the status bar. */
|
|
38
|
-
export type ConnectionState = "live" | "reconnecting" | "offline";
|
|
39
|
-
|
|
40
3
|
/**
|
|
41
4
|
* Which tree rows are open.
|
|
42
5
|
*
|
|
43
6
|
* Client-owned, deliberately: see the module header. An interface rather than
|
|
44
7
|
* a bare array so that P2's filter text and provenance-cycling state have an
|
|
45
|
-
* obvious home that does not change this
|
|
8
|
+
* obvious home that does not change this state shape.
|
|
46
9
|
*/
|
|
47
|
-
export interface
|
|
48
|
-
|
|
49
|
-
|
|
10
|
+
export interface WorkspaceState {
|
|
11
|
+
graph: GraphPayload | null;
|
|
12
|
+
graphFailed: boolean;
|
|
13
|
+
note: NotePayload | null;
|
|
14
|
+
recentIds: ReadonlySet<string>;
|
|
15
|
+
selectedId: string | null;
|
|
50
16
|
}
|
|
51
17
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return { expanded: [] };
|
|
18
|
+
export function initialWorkspaceState(): WorkspaceState {
|
|
19
|
+
return { graph: null, graphFailed: false, note: null, recentIds: new Set(), selectedId: null };
|
|
55
20
|
}
|
|
56
|
-
|
|
57
|
-
/** The graph node id currently in context — everywhere. */
|
|
58
|
-
export const selectedId = signal<string | null>(null);
|
|
59
|
-
|
|
60
|
-
/** The whole graph, as delivered by the server. `null` until first load. */
|
|
61
|
-
export const graph = signal<GraphPayload | null>(null);
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Whether the *first* graph fetch failed.
|
|
65
|
-
*
|
|
66
|
-
* Distinct from {@link connection} on purpose: the socket can be live while a
|
|
67
|
-
* fetch 500s, and "offline" is then the wrong sentence. With a graph already
|
|
68
|
-
* on screen a failed refetch leaves the stale value standing (see
|
|
69
|
-
* `workspace.ts`'s header — a stale graph beats a blank one), so this only
|
|
70
|
-
* ever describes the boot, which is the one moment there is nothing to show.
|
|
71
|
-
*/
|
|
72
|
-
export const graphFailed = signal(false);
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Derived: fetched when {@link selectedId} names a note.
|
|
76
|
-
*
|
|
77
|
-
* A {@link NotePayload} rather than a bare `ViewNote` as of P5. The revision
|
|
78
|
-
* travels **with** the body because the editor saves against it, and a
|
|
79
|
-
* revision fetched separately would describe a state the draft was not typed
|
|
80
|
-
* against — the exact window a conflict check exists to close. The note
|
|
81
|
-
* column reads `.note` and is otherwise unchanged.
|
|
82
|
-
*/
|
|
83
|
-
export const noteBody = signal<NotePayload | null>(null);
|
|
84
|
-
|
|
85
|
-
/** Which tree rows are open. */
|
|
86
|
-
export const treeState = signal<TreeState>(initialTreeState());
|
|
87
|
-
|
|
88
|
-
/** SSE liveness (§6). */
|
|
89
|
-
export const connection = signal<ConnectionState>("live");
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Node ids that appeared in the most recent graph update (weave-workspace
|
|
93
|
-
* §6 frame → tree feedback).
|
|
94
|
-
*
|
|
95
|
-
* Empty on the mount fetch — a first load must not animate the whole tree.
|
|
96
|
-
* The controller replaces the set on every published payload and clears it
|
|
97
|
-
* after {@link ../workspace!RECENT_TTL_MS}, so a row collapsed and re-expanded
|
|
98
|
-
* minutes later does not replay its arrival animation.
|
|
99
|
-
*/
|
|
100
|
-
export const recentIds = signal<ReadonlySet<string>>(new Set());
|