@augurworks/augur 0.15.1
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/CANVAS.md +858 -0
- package/INSTALL.md +603 -0
- package/LICENSE +27 -0
- package/README.md +150 -0
- package/TRADEMARKS.md +72 -0
- package/agents/README.md +65 -0
- package/agents/canon-extract.md +117 -0
- package/agents/canon.md +151 -0
- package/agents/canvas.md +88 -0
- package/agents/currency.md +74 -0
- package/agents/drafts.md +92 -0
- package/agents/identity.md +148 -0
- package/agents/prototype-contract.md +69 -0
- package/agents/publishing.md +324 -0
- package/agents/review-feedback.md +76 -0
- package/agents/space-json.md +132 -0
- package/agents/ui-skill.md +101 -0
- package/agents/working-marks.md +86 -0
- package/brand/augur-eye.svg +3 -0
- package/brand/augur-icon.svg +41 -0
- package/brand/augur-mark.svg +5 -0
- package/brand/lentianova-bulky.otf +0 -0
- package/brand/lentianova-median.otf +0 -0
- package/brand/lentianova.woff2 +0 -0
- package/build.js +8409 -0
- package/changelog.md +133 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/package.json +57 -0
- package/pitis/PETS.md +121 -0
- package/pitis/README.md +117 -0
- package/pitis/index.html +266 -0
- package/pitis/piti.build.js +81 -0
- package/pitis/piti.js +929 -0
- package/pitis/roast-agent.md +185 -0
- package/realtime/src/index.js +119 -0
- package/realtime/wrangler.example.toml +44 -0
- package/scripts/adopt.mjs +106 -0
- package/scripts/board-snapshot.mjs +197 -0
- package/scripts/build-seed-pack.mjs +33 -0
- package/scripts/bundle-rekey.mjs +112 -0
- package/scripts/bundle-tenancy-rehearsal.mjs +781 -0
- package/scripts/canon-extract.mjs +578 -0
- package/scripts/canon.mjs +599 -0
- package/scripts/canvas-screen.mjs +333 -0
- package/scripts/check-local.mjs +136 -0
- package/scripts/clawd-art.mjs +75 -0
- package/scripts/clawd-canvas.mjs +741 -0
- package/scripts/cli.mjs +100 -0
- package/scripts/clone.mjs +422 -0
- package/scripts/close.mjs +30 -0
- package/scripts/connect.mjs +103 -0
- package/scripts/deploy.mjs +157 -0
- package/scripts/dev.mjs +54 -0
- package/scripts/doc-lint.mjs +143 -0
- package/scripts/export.mjs +300 -0
- package/scripts/fork.mjs +64 -0
- package/scripts/freeze.mjs +74 -0
- package/scripts/frontdoor-parity.mjs +180 -0
- package/scripts/hook.mjs +67 -0
- package/scripts/identity-rekey.mjs +112 -0
- package/scripts/init.mjs +122 -0
- package/scripts/land.mjs +40 -0
- package/scripts/lib/adapters.mjs +156 -0
- package/scripts/lib/board-snapshot.mjs +511 -0
- package/scripts/lib/draft.mjs +385 -0
- package/scripts/lib/graduate.mjs +276 -0
- package/scripts/lib/instance.mjs +74 -0
- package/scripts/lib/marks.mjs +107 -0
- package/scripts/lib/materialize.mjs +138 -0
- package/scripts/lib/merge3.mjs +124 -0
- package/scripts/lib/offline-posture.mjs +120 -0
- package/scripts/lib/offline-respawn.mjs +18 -0
- package/scripts/lib/offline-wrangler.mjs +78 -0
- package/scripts/lib/publish-compose.mjs +12 -0
- package/scripts/lib/publish-conflict.mjs +78 -0
- package/scripts/lib/publish-evidence.mjs +144 -0
- package/scripts/lib/refine-compare.mjs +140 -0
- package/scripts/lib/refine-ledger.mjs +150 -0
- package/scripts/lib/refine-png.mjs +149 -0
- package/scripts/lib/seed-pack-build.mjs +141 -0
- package/scripts/lib/state-compare.mjs +68 -0
- package/scripts/lib/store.mjs +122 -0
- package/scripts/login.mjs +84 -0
- package/scripts/mark.mjs +112 -0
- package/scripts/migrate-board-keys.mjs +164 -0
- package/scripts/migrate.mjs +265 -0
- package/scripts/no-foreign-vocabulary.mjs +216 -0
- package/scripts/no-product-names.mjs +157 -0
- package/scripts/no-tenant-globals.mjs +1134 -0
- package/scripts/offline.mjs +247 -0
- package/scripts/og.mjs +170 -0
- package/scripts/one-tenant-resolver.mjs +146 -0
- package/scripts/open.mjs +63 -0
- package/scripts/publish.mjs +1041 -0
- package/scripts/read.mjs +31 -0
- package/scripts/refine.mjs +455 -0
- package/scripts/release-drift.mjs +138 -0
- package/scripts/restore.mjs +322 -0
- package/scripts/review.mjs +125 -0
- package/scripts/save.mjs +21 -0
- package/scripts/shell-lint.mjs +189 -0
- package/scripts/ship.mjs +460 -0
- package/scripts/shoot.mjs +264 -0
- package/scripts/smoke-invite.mjs +113 -0
- package/scripts/state-inventory.mjs +223 -0
- package/scripts/status.mjs +177 -0
- package/scripts/sync.mjs +27 -0
- package/scripts/tenant-do-rehearsal.mjs +1314 -0
- package/scripts/ui-copy-lint.mjs +202 -0
- package/scripts/watch.mjs +32 -0
- package/scripts/wrangler-preflight.mjs +246 -0
- package/seed/CANON.md +43 -0
- package/seed/README.md +119 -0
- package/seed/prototype-status.json +9 -0
- package/seed/registry.json +88 -0
- package/seed/skills/starter-ui/SKILL.md +52 -0
- package/seed/skills/starter-ui/skill.json +11 -0
- package/seed/skills/starter-ui/starter-tokens.css +73 -0
- package/seed/skills/starter-ui/starter-ui.css +318 -0
- package/seed/skills/starter-ui/starter-ui.js +50 -0
- package/seed/space.json +5 -0
- package/seed/start-here/prototypes/connect-your-terminal/index.html +428 -0
- package/seed/start-here/prototypes/connect-your-terminal/preview.webp +0 -0
- package/seed/start-here/prototypes/sample-with-comments/index.html +172 -0
- package/seed/start-here/prototypes/sample-with-comments/preview.webp +0 -0
- package/seed/start-here/prototypes/set-up-your-design-system/index.html +353 -0
- package/seed/start-here/prototypes/set-up-your-design-system/preview.webp +0 -0
- package/seed/threads.json +52 -0
- package/seed/worked-examples/prototypes/field-readings/index.html +273 -0
- package/seed/worked-examples/prototypes/field-readings/preview.webp +0 -0
- package/seed/worked-examples/prototypes/slide-deck/index.html +214 -0
- package/seed/worked-examples/prototypes/slide-deck/preview.webp +0 -0
- package/seed/worked-examples/prototypes/specimen-viewer/index.html +203 -0
- package/seed/worked-examples/prototypes/specimen-viewer/preview.webp +0 -0
- package/src/_worker.js +12668 -0
- package/src/board-key.mjs +57 -0
- package/src/board-room.mjs +881 -0
- package/src/bundle-keys.mjs +138 -0
- package/src/canon/collect-in-browser.js +215 -0
- package/src/canon/emit.mjs +232 -0
- package/src/canon/observe.mjs +260 -0
- package/src/canon/schema.mjs +451 -0
- package/src/canvas/DSEG-LICENSE.txt +95 -0
- package/src/canvas/DSEG7Classic-Bold.woff2 +0 -0
- package/src/canvas/canvas.css +885 -0
- package/src/canvas/canvas.js +5780 -0
- package/src/canvas/capture.js +485 -0
- package/src/chrome/appchrome.mjs +568 -0
- package/src/currency.mjs +203 -0
- package/src/delete-confirmation.mjs +254 -0
- package/src/drafts/drafts.js +326 -0
- package/src/entry.js +58 -0
- package/src/galleries.mjs +400 -0
- package/src/health-cron.mjs +173 -0
- package/src/identity.json +1 -0
- package/src/kv-codec.mjs +135 -0
- package/src/kv-identity.mjs +259 -0
- package/src/mail.mjs +599 -0
- package/src/provenance.mjs +74 -0
- package/src/publish-compose.mjs +363 -0
- package/src/publish-fork.mjs +221 -0
- package/src/publish-units.mjs +44 -0
- package/src/purge.mjs +81 -0
- package/src/review/COMMENTING-UX.md +306 -0
- package/src/review/cat.png +0 -0
- package/src/review/comment-cursor.svg +7 -0
- package/src/review/comments.js +1738 -0
- package/src/room-ticket.mjs +96 -0
- package/src/roster-chip.mjs +32 -0
- package/src/seed-pack.mjs +261 -0
- package/src/state-inventory.mjs +249 -0
- package/src/sw-logic.mjs +48 -0
- package/src/tenant-cache.mjs +141 -0
- package/src/tenant-context.mjs +386 -0
- package/src/tenant-do.js +2940 -0
- package/src/tenant-host.mjs +144 -0
- package/src/tenant-quotas.mjs +140 -0
- package/src/unit-core.mjs +103 -0
- package/src/unit-object.mjs +338 -0
- package/templates/README.md +67 -0
- package/templates/shell/deploy.yml +145 -0
- package/templates/shell/engine-bump.yml +142 -0
- package/templates/shell/health.yml +233 -0
- package/templates/shell/kv-backup.yml +201 -0
- package/templates/shell/roster-update.yml +110 -0
- package/templates/shell/space-preflight.yml +35 -0
- package/templates/shell/space-rebake.yml +79 -0
- package/templates/shell/store-backup.yml +152 -0
- package/templates/shell/wrangler.example.toml +266 -0
- package/templates/space/publish.yml +91 -0
package/src/purge.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Redacting one person out of a conversation other people are part of.
|
|
2
|
+
//
|
|
3
|
+
// `E-gdpr-purge-user`. Extracted from src/_worker.js so the WORKSPACE OBJECT can run the
|
|
4
|
+
// same sweep the worker runs. Under Decision 2 an erasure has to happen in every workspace
|
|
5
|
+
// the account belongs to, and only the control plane knows which those are — so the sweep
|
|
6
|
+
// has to be reachable as a workspace verb, not only as an admin action inside one workspace
|
|
7
|
+
// by somebody who happens to administer it. Two copies of this logic would be two answers
|
|
8
|
+
// to "was this person erased", and the one that gets used less is the one that rots.
|
|
9
|
+
//
|
|
10
|
+
// ── WHAT AN ERASURE KEEPS ───────────────────────────────────────────────────────────
|
|
11
|
+
//
|
|
12
|
+
// `body` and `at` survive, and so does thread structure. Deleting the messages would erase
|
|
13
|
+
// other people's conversation — a reply that answers a question is unreadable once the
|
|
14
|
+
// question is gone — and the request is to stop identifying somebody, not to rewrite a
|
|
15
|
+
// record other people are part of. So the message stays and stops carrying a person:
|
|
16
|
+
// `author` becomes a fixed sentinel, `by` is cleared, and `verified` goes false so nothing
|
|
17
|
+
// renders it as a confirmed identity.
|
|
18
|
+
//
|
|
19
|
+
// ── ⚠️ IDENTIFICATION IS BY A 32-BIT HASH, AND THAT IS THE SHARP EDGE ───────────────
|
|
20
|
+
//
|
|
21
|
+
// Messages store `by: personId(email)`, a one-way djb2 hash — deliberately, because an
|
|
22
|
+
// address in every stored message would be reversible PII and `/__people` is ungated on
|
|
23
|
+
// public prototypes precisely because ids cannot be reversed. Do NOT "fix" that by storing
|
|
24
|
+
// the address.
|
|
25
|
+
//
|
|
26
|
+
// The consequence is that two addresses can share an id, and a purge keyed on it would then
|
|
27
|
+
// redact an innocent third party's messages too. A machine cannot choose between them, so
|
|
28
|
+
// nothing here tries: the caller checks the workspace roster for any OTHER member sharing
|
|
29
|
+
// the id and REFUSES, naming the count. That turns a silent over-redaction into a question
|
|
30
|
+
// for a person, which is the only honest answer available.
|
|
31
|
+
|
|
32
|
+
/** What a redacted message says instead of a name. One string, so nothing invents a second. */
|
|
33
|
+
export const PURGED_AUTHOR = "Deleted user";
|
|
34
|
+
|
|
35
|
+
/** The address folding this module uses. Matches the worker's `lcEmail`. */
|
|
36
|
+
export const lcAddress = (e) => String(e == null ? "" : e).trim().toLowerCase();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The one-way author id. djb2 over the folded address, base 36.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ ITS OUTPUT IS STORED IN EVERY MESSAGE EVER WRITTEN, so this function is a data format
|
|
42
|
+
* and not an implementation detail. Changing it orphans every stored `by`, which means every
|
|
43
|
+
* past erasure silently stops matching and every "is this mine" check answers no.
|
|
44
|
+
*/
|
|
45
|
+
export function personIdFor(email) {
|
|
46
|
+
const s = lcAddress(email);
|
|
47
|
+
let h = 5381;
|
|
48
|
+
for (let i = 0; i < s.length; i++) h = (h * 33 + s.charCodeAt(i)) >>> 0;
|
|
49
|
+
return h.toString(36);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Redact one person from one thread array. Pure — every caller does its own I/O. */
|
|
53
|
+
export function purgeThreads(threads, id) {
|
|
54
|
+
let redacted = 0;
|
|
55
|
+
const out = (Array.isArray(threads) ? threads : []).map((t) => {
|
|
56
|
+
if (!t || !Array.isArray(t.messages)) return t;
|
|
57
|
+
let touched = false;
|
|
58
|
+
const messages = t.messages.map((m) => {
|
|
59
|
+
if (!m || m.by !== id) return m;
|
|
60
|
+
touched = true; redacted++;
|
|
61
|
+
// Spread first so any field a future version adds survives an erasure written before
|
|
62
|
+
// it existed; the three that identify are then overwritten by name.
|
|
63
|
+
return { ...m, author: PURGED_AUTHOR, by: null, verified: false };
|
|
64
|
+
});
|
|
65
|
+
return touched ? { ...t, messages } : t;
|
|
66
|
+
});
|
|
67
|
+
return { threads: out, redacted };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Which OTHER members share this address's author id. Empty means the sweep is safe.
|
|
72
|
+
* `members` is anything with an `email` — the worker's roster rows and the workspace
|
|
73
|
+
* object's `members` table both qualify.
|
|
74
|
+
*/
|
|
75
|
+
export function idCollisions(members, email) {
|
|
76
|
+
const addr = lcAddress(email);
|
|
77
|
+
const id = personIdFor(addr);
|
|
78
|
+
return (members || [])
|
|
79
|
+
.map((u) => lcAddress(u && u.email))
|
|
80
|
+
.filter((e) => e && e !== addr && personIdFor(e) === id);
|
|
81
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# Commenting UX — compose & thread redesign (build plan)
|
|
2
|
+
|
|
3
|
+
> **Historical** — the original build plan for the comments overlay, kept as a design record.
|
|
4
|
+
> Current behavior is the code (`src/review/comments.js`); do not treat "what's there today" claims below as current.
|
|
5
|
+
|
|
6
|
+
Target spec for upgrading the review overlay (`comments.js`) to a modern
|
|
7
|
+
commenting interaction model, captured from a set of annotated reference
|
|
8
|
+
screenshots. This is a **build plan**, not a description of
|
|
9
|
+
current behavior: each state below states the *target*, the *interaction detail*
|
|
10
|
+
(transitions, focus, keyboard, edge cases), and **what's there today vs. what to
|
|
11
|
+
build** against the current implementation.
|
|
12
|
+
|
|
13
|
+
> Scope note: the overlay lives entirely in `src/review/comments.js` (shadow-DOM,
|
|
14
|
+
> single IIFE). Everything here is implementable inside that file + its stylesheet
|
|
15
|
+
> block (`root.innerHTML` `<style>`, comments.js:211). KV/worker changes are flagged
|
|
16
|
+
> where a state needs persisted data we don't store yet.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## State machine (overview)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
click canvas first keypress
|
|
24
|
+
(browse) ─────────────▶ [pin + collapsed input] ─────────────▶ [compose box + toolbar]
|
|
25
|
+
│ Esc / blur-empty │ text grows box
|
|
26
|
+
└──────────────┐ │ @ / emoji / image
|
|
27
|
+
▼ ▼
|
|
28
|
+
(discard) submit (⏎ / arrow)
|
|
29
|
+
│
|
|
30
|
+
hover pin ▼
|
|
31
|
+
[numbered pin] ───────────▶ [hover preview card] ──click──▶ [open thread]
|
|
32
|
+
│
|
|
33
|
+
┌───────────────────────┤
|
|
34
|
+
reply box (same compose UI) resolve ✓ / close ✕ / ⋯
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Five visible compose/read surfaces, mapped to the reference image's six panels:
|
|
38
|
+
|
|
39
|
+
| Reference panel | Surface |
|
|
40
|
+
|------------------------|------------------------------------------|
|
|
41
|
+
| *when dropping pin* | Collapsed compose (pin + 1-line input) |
|
|
42
|
+
| *when starting to write* | Expanded compose (box + toolbar) |
|
|
43
|
+
| *longer write* | Expanded compose, auto-grown + @mentions |
|
|
44
|
+
| (emoji grid) | Emoji picker popover |
|
|
45
|
+
| *preview on hover* | Hover preview card (read-only) |
|
|
46
|
+
| (open thread + reply) | Open thread + reply compose |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 1. Dropping a pin — collapsed compose
|
|
51
|
+
|
|
52
|
+
**Target.** Clicking the canvas drops the pin glyph and, immediately to its right,
|
|
53
|
+
a single-line pill input reading **"Add a comment"** with a circular send arrow on
|
|
54
|
+
the trailing edge (disabled/greyed until there's text). No toolbar yet, no name
|
|
55
|
+
field, nothing else. It reads as one lightweight affordance, not a form.
|
|
56
|
+
|
|
57
|
+
**Interaction detail.**
|
|
58
|
+
- The pin appears at the click point; the input anchors beside it (right by
|
|
59
|
+
default, flips left if it would overflow the viewport — reuse the clamp logic in
|
|
60
|
+
`positionCard`, comments.js:481).
|
|
61
|
+
- Input autofocuses. Caret is in the field; the user can type immediately.
|
|
62
|
+
- **Esc** or **clicking away while empty** discards the whole thing (pin + input)
|
|
63
|
+
with no persistence — this is a *pending* pin, not a saved comment.
|
|
64
|
+
- Send arrow is disabled while empty; pressing **⏎** with text submits (see §2).
|
|
65
|
+
- The pin has no number yet — it's numbered only once the comment is saved and
|
|
66
|
+
enters `listed()` (comments.js:338).
|
|
67
|
+
|
|
68
|
+
**Today vs. build.**
|
|
69
|
+
- Today: the catcher click goes straight to `composeNew` (comments.js:631, :490),
|
|
70
|
+
which renders the *full* card (heading, optional name input, multi-line
|
|
71
|
+
textarea, anno toggle, Cancel/Comment buttons). There is no collapsed step.
|
|
72
|
+
- Build: split `composeNew` into a **collapsed → expanded** progression. Start
|
|
73
|
+
with a compact `.compose.collapsed` element (pin + 1-line input + send). Keep the
|
|
74
|
+
`anchorAt()` capture (comments.js:179) exactly as-is — that's the pin's anchor and
|
|
75
|
+
is orthogonal to the compose chrome.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. Starting to write — expanded compose + toolbar
|
|
80
|
+
|
|
81
|
+
**Target.** On the first keypress the collapsed pill **expands** into a rounded
|
|
82
|
+
card: a multi-line text area on top, and a toolbar row beneath it with three icon
|
|
83
|
+
buttons — **emoji** (☺), **@mention**, **image** — on the left, and the blue
|
|
84
|
+
circular **send** arrow on the right (now enabled).
|
|
85
|
+
|
|
86
|
+
**Interaction detail.**
|
|
87
|
+
- Transition is the input *growing into* the box, not a swap — the typed text
|
|
88
|
+
stays, caret position preserved. Animate height if cheap; never lose focus.
|
|
89
|
+
- Toolbar buttons:
|
|
90
|
+
- **Emoji** → opens the emoji picker popover (§4); inserts at caret.
|
|
91
|
+
- **@mention** → inserts `@` at caret and opens the mention autocomplete (§3).
|
|
92
|
+
- **Image** → file picker; see §6 for the storage decision (currently unsolved).
|
|
93
|
+
- **Send**: blue filled circle + up-arrow. Enabled iff trimmed text is non-empty.
|
|
94
|
+
Click or **⏎** submits. **Shift+⏎** inserts a newline (don't submit).
|
|
95
|
+
- On submit: persist via `mutate({op:"add", …})` (comments.js:522), collapse the
|
|
96
|
+
compose, and render the saved pin (numbered) + open thread — first send drops
|
|
97
|
+
you into the open thread.
|
|
98
|
+
|
|
99
|
+
**Today vs. build.**
|
|
100
|
+
- Today: the textarea exists (`.tx`, comments.js:498) but there's **no toolbar**,
|
|
101
|
+
no emoji/@/image, and submit is a labelled "Comment" button (comments.js:500) with
|
|
102
|
+
no ⏎-to-send.
|
|
103
|
+
- Build: add the toolbar row + send-arrow button; wire ⏎/Shift+⏎ key handling on
|
|
104
|
+
the textarea; auto-grow (§3). The name capture (`needName`, comments.js:496) should
|
|
105
|
+
move out of the inline form — the reference identifies the author by avatar, not a
|
|
106
|
+
text field. Capture the name **once** (first-run prompt or a small identity chip), then
|
|
107
|
+
represent the author as an avatar everywhere (see §7, Open questions).
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 3. Longer write — auto-grow + @mentions
|
|
112
|
+
|
|
113
|
+
**Target.** As content grows the box grows with it (the *"auto-box increase"* in
|
|
114
|
+
the reference). `@`-mentions render inline as a coloured token (the name in the
|
|
115
|
+
accent colour, e.g. "Ana" in blue), distinct from body text.
|
|
116
|
+
|
|
117
|
+
**Interaction detail.**
|
|
118
|
+
- **Auto-grow:** textarea height tracks content up to a max (then it scrolls).
|
|
119
|
+
Implement by setting `height:auto; height:scrollHeight` on each `input` event, or
|
|
120
|
+
a mirror/`field-sizing` approach. The toolbar stays pinned to the bottom edge.
|
|
121
|
+
- **@mention flow:** typing `@` opens an autocomplete list of known
|
|
122
|
+
participants/teammates; arrow-keys + ⏎ to pick; Esc closes the list without
|
|
123
|
+
inserting. A committed mention is a styled, atomic token — backspace deletes the
|
|
124
|
+
whole token, not one character.
|
|
125
|
+
- Rendering mentions requires storing the message as **rich content** (text +
|
|
126
|
+
mention spans), not the current plain `body` string (comments.js:520). Either a
|
|
127
|
+
lightweight markup (`@[name](id)`) parsed at render, or a structured
|
|
128
|
+
`{ text, mentions:[{offset,len,id,name}] }`. Pick one and use it for both compose
|
|
129
|
+
and the message list (§6). **Never** `innerHTML` raw user text — the current code
|
|
130
|
+
correctly uses `textContent` (comments.js:549); the mention renderer must tokenise
|
|
131
|
+
and build nodes, not interpolate strings.
|
|
132
|
+
|
|
133
|
+
**Today vs. build.**
|
|
134
|
+
- Today: fixed `min-height:64px; resize:vertical` textarea (comments.js:273); plain
|
|
135
|
+
string body; no mentions.
|
|
136
|
+
- Build: auto-grow handler; rich-content model; mention autocomplete + token
|
|
137
|
+
rendering. **Mention directory is an open question** — see §7.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 4. Emoji picker
|
|
142
|
+
|
|
143
|
+
**Target.** A popover with: a category tab strip across the top (recent ⏱, smileys,
|
|
144
|
+
nature, food, activity, travel, objects, symbols, flags), a **Search** field, a
|
|
145
|
+
**Frequently used** row, then **Smileys & People** and the rest in a scrollable
|
|
146
|
+
grid. A skin-tone selector sits bottom-right.
|
|
147
|
+
|
|
148
|
+
**Interaction detail.**
|
|
149
|
+
- Opens anchored to the emoji toolbar button; flips to stay on-screen (reuse the
|
|
150
|
+
viewport clamp pattern from `showTip`, comments.js:455).
|
|
151
|
+
- Picking an emoji inserts it at the caret and keeps the picker
|
|
152
|
+
open for multi-pick; click-away or Esc closes it.
|
|
153
|
+
- Search filters the grid live by name/keyword.
|
|
154
|
+
- "Frequently used" is per-user, persisted to `localStorage` (same pattern as
|
|
155
|
+
`LS_NAME`, comments.js:42) — no server needed.
|
|
156
|
+
|
|
157
|
+
**Today vs. build.** Not present at all. This is the heaviest new piece.
|
|
158
|
+
- Build: ship a small emoji dataset (categories + keywords) as a local module —
|
|
159
|
+
**do not** pull a CDN/npm picker; prototypes are self-contained static files
|
|
160
|
+
(CLAUDE.md). A compact JSON of common emoji + a simple grid renderer inside the
|
|
161
|
+
shadow root keeps it dependency-free. Defer the full Unicode set unless asked;
|
|
162
|
+
"Frequently used + Smileys & People + search" covers the reference.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 5. Preview on hover
|
|
167
|
+
|
|
168
|
+
**Target.** Hovering a pin shows a small read-only **preview card**: author
|
|
169
|
+
name, relative time ("just now"), and the comment body truncated with an ellipsis
|
|
170
|
+
(*"Adding a really long comment, I really don't need the @ or the images (well, can
|
|
171
|
+
you highlight…"*). It does **not** show replies or any input — it's a peek. Click
|
|
172
|
+
opens the full thread (§6).
|
|
173
|
+
|
|
174
|
+
**Interaction detail.**
|
|
175
|
+
- **Grow-from-pin animation (the reference image):** the card **unfurls out of the
|
|
176
|
+
pin, left edge → right**. The pin sits at the card's left; the card scales/expands
|
|
177
|
+
rightward from there. Implement with `transform-origin: left center` + a
|
|
178
|
+
`scaleX`/width-and-opacity transition (~160–200ms, ease-out); the pin stays put and
|
|
179
|
+
the body grows out of it. (Today's `.atip` grows *upward* from centre,
|
|
180
|
+
comments.js:228–232 — this is a different origin: anchor left, expand right.)
|
|
181
|
+
- Default placement is to the **right** of the pin; flip to the left if it would
|
|
182
|
+
overflow the right edge (mirror the viewport clamp in `showTip`, comments.js:455).
|
|
183
|
+
- Hover-intent: small delay in (~150ms) so sweeping the canvas doesn't flash cards;
|
|
184
|
+
hide on mouse-leave unless the cursor moves onto the preview itself.
|
|
185
|
+
- Truncate body to ~3 lines via CSS line-clamp; never render the full thread here.
|
|
186
|
+
- Touch: no hover — tap opens the thread directly.
|
|
187
|
+
|
|
188
|
+
**Today vs. build.**
|
|
189
|
+
- Today: hover preview exists **only for annotations in delivery mode** — the blue
|
|
190
|
+
`.atip` bubble shows the note text (comments.js:451, :228). Normal comment pins in
|
|
191
|
+
review mode have only a native `title` tooltip (comments.js:396).
|
|
192
|
+
- Build: extend hover preview to all pins in review mode, upgraded from a text
|
|
193
|
+
bubble to an author + relative-time + snippet card (no avatar) that grows from the
|
|
194
|
+
pin left→right. The annotation delivery-mode bubble can stay as-is (it's a
|
|
195
|
+
different, simpler affordance) or share the card styling.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 6. Open thread + reply
|
|
200
|
+
|
|
201
|
+
> **Copy this card as-is — the user loves it.** Replicate the layout, spacing, and
|
|
202
|
+
> chrome of the reference exactly: rounded white card, "Comment" header, bold
|
|
203
|
+
> name + grey relative time per message, the accent-coloured `@mention` and
|
|
204
|
+
> name token in body text, and the rounded grey **"Reply"** pill with an inner
|
|
205
|
+
> circular send arrow on its trailing edge. Match it pixel-faithfully, then map the
|
|
206
|
+
> header icons to our semantics below. **Avatars:** a signed-in author
|
|
207
|
+
> renders as their photo or initials; an anonymous one keeps the bold name and a
|
|
208
|
+
> numbered pin. **White only** — no dark-mode
|
|
209
|
+
> variant.
|
|
210
|
+
|
|
211
|
+
**Target.** The full thread card. Header: **"Comment"** title on the left; on the
|
|
212
|
+
right, **four** icon buttons in this order — **⋯**, the **annotation (cat) toggle**,
|
|
213
|
+
**resolve ✓** (circle-check), and **✕** close. Body: each message as bold
|
|
214
|
+
name + grey relative time + per-message **⋯** + the text. Footer: a **reply** compose
|
|
215
|
+
box (collapsed "Reply" pill → expands to the §2–4 toolbar compose on focus).
|
|
216
|
+
|
|
217
|
+
**Header icon mapping (our semantics — not the reference's):**
|
|
218
|
+
|
|
219
|
+
| Icon | Our action |
|
|
220
|
+
|-------------------------|-------------------------------------------------------------------|
|
|
221
|
+
| **⋯** (three dots) | **Delete** the thread (this is our delete affordance, not a menu) |
|
|
222
|
+
| **cat avatar** | **Annotation toggle** — promote/demote the always-on dev note |
|
|
223
|
+
| **✓** (circle-check) | **Resolve** / reopen |
|
|
224
|
+
| **✕** | **Close** the card (browse stays on) |
|
|
225
|
+
|
|
226
|
+
**Interaction detail.**
|
|
227
|
+
- **⋯ = Delete.** It is *not* an overflow menu — clicking it deletes the thread
|
|
228
|
+
(keep the existing confirm, comments.js:568–571). Style it as a three-dot
|
|
229
|
+
glyph, in the header's top-right cluster.
|
|
230
|
+
- **Cat toggle** sits next to resolve, carrying the annotation promotion that's
|
|
231
|
+
inline today (`anno-toggle`, comments.js:561). Same on/off visual it already has
|
|
232
|
+
(greyscale ↔ amber border, comments.js:264–266), just relocated into the header
|
|
233
|
+
row. Keep the title/tooltip copy.
|
|
234
|
+
- **Resolve ✓** toggles resolved (green pin, comments.js:218); resolved threads stay
|
|
235
|
+
listed and reopenable. **✕** closes the card.
|
|
236
|
+
- Each message's **⋯** → delete that message (per-message; today only whole threads
|
|
237
|
+
delete). Lower priority than the header cluster — build last.
|
|
238
|
+
- Reply: collapsed grey **"Reply"** pill with inner send arrow (disabled until text);
|
|
239
|
+
on focus/keypress it expands into the same toolbar compose as §2–4 (auto-grow, ⏎
|
|
240
|
+
sends, Shift+⏎ newline).
|
|
241
|
+
- **Relative time:** show "Just now" / "1 minute ago". Replace the absolute
|
|
242
|
+
`fmt()` (comments.js:611) with a relative formatter (re-tick open cards on an
|
|
243
|
+
interval so "Just now" ages to "1 minute ago").
|
|
244
|
+
|
|
245
|
+
**Today vs. build.**
|
|
246
|
+
- Today: header is `Comment/Annotation · resolved` text + an inline anno-toggle
|
|
247
|
+
avatar (comments.js:533–537); actions are labelled buttons in a footer row
|
|
248
|
+
(Delete / Resolve / Reply, comments.js:540–542); messages are name + absolute time
|
|
249
|
+
+ body, no avatar, no per-message menu (comments.js:544–551); reply is a plain
|
|
250
|
+
textarea (comments.js:539).
|
|
251
|
+
- Build: restyle header to the four icon buttons (⋯ delete / cat annotate / ✓ resolve
|
|
252
|
+
/ ✕ close); add per-message ⋯ (delete message, last); swap the reply textarea for
|
|
253
|
+
the expanding compose pill; relative timestamps; remove dark mode.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 7. Open questions / decisions before building
|
|
258
|
+
|
|
259
|
+
**Locked for v1:** no avatars · white only · build all at once · **emoji, @mentions,
|
|
260
|
+
and images all deferred** → the compose box has **no toolbar row** for now (just the
|
|
261
|
+
auto-growing field + send arrow). Native OS emoji can still be typed into the field.
|
|
262
|
+
The annotation (cat) affordance is **removed from compose** — you create a plain
|
|
263
|
+
comment, then promote it via the cat icon in the open-thread header (§6).
|
|
264
|
+
|
|
265
|
+
These aren't visible in the image but the build can't proceed cleanly without them:
|
|
266
|
+
|
|
267
|
+
1. **Avatars / identity — DECIDED.** Users exist (invite-only accounts, an admin
|
|
268
|
+
people table, per-person photo/initials/colour), so comments carry a `by` id
|
|
269
|
+
stamped from the session and resolve to a face via `/__people`.
|
|
270
|
+
Anonymous comments keep the numbered pin.
|
|
271
|
+
2. **Theme — DECIDED: white only.** Remove the `prefers-color-scheme: dark` block
|
|
272
|
+
(comments.js:282–292). Go all-white for compose/thread chrome.
|
|
273
|
+
4. **@mention directory.** Where does the autocomplete list come from? There's no
|
|
274
|
+
user list in a static prototype. Options: a hardcoded teammate list per
|
|
275
|
+
prototype, or free-text `@anything` that just styles the token without resolving
|
|
276
|
+
to a real user. **Recommend free-text styled tokens** unless a real list exists.
|
|
277
|
+
5. **Image upload.** KV stores small JSON thread blobs (comments.js:88); it's not an
|
|
278
|
+
image store. Options: base64-inline small images into the message (bloats KV,
|
|
279
|
+
simplest), upload to R2/an asset endpoint (new worker route), or **drop image
|
|
280
|
+
support for v1** and ship emoji + @ only (the reference author even says *"I don't
|
|
281
|
+
need images"*). **Recommend deferring images.**
|
|
282
|
+
6. **Persistence shape.** Mentions/rich content change the stored `message` schema
|
|
283
|
+
(comments.js:519). Decide the model once (plain string vs. structured) — it
|
|
284
|
+
touches add, reply, render, and the resolve tooling (`scripts/review.mjs`).
|
|
285
|
+
7. **Scope.** Build all surfaces at once, or stage it? Suggested order that keeps the
|
|
286
|
+
overlay shippable at each step: **(1)** collapsed→expanded compose + ⏎-to-send +
|
|
287
|
+
auto-grow → **(2)** relative time + header icon buttons + remove dark mode →
|
|
288
|
+
**(3)** hover preview card → **(4)** emoji picker → **(5)** @mentions →
|
|
289
|
+
**(6)** images (if at all).
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Invariants to preserve (don't regress these)
|
|
294
|
+
|
|
295
|
+
- **Shadow-DOM isolation** — all UI stays inside `root` (comments.js:208); never
|
|
296
|
+
leak styles to the host page.
|
|
297
|
+
- **No `innerHTML` of user text** — tokenise/`textContent` only (comments.js:549).
|
|
298
|
+
- **Screen contract** — pins stay anchored via `anchorAt`/`pinXY` + `data-gv-screen`
|
|
299
|
+
scoping (comments.js:179, :136); compose chrome must not touch anchoring.
|
|
300
|
+
- **KV-or-localStorage fallback** — every new field must round-trip through both
|
|
301
|
+
`mutate`/`apiCall` and `applyLocal` (comments.js:88–108).
|
|
302
|
+
- **Annotations** — the cat-avatar always-on delivery notes (comments.js:110, :340)
|
|
303
|
+
and their resolve-tooling exemption stay intact; the annotation toggle just moves
|
|
304
|
+
into the thread ⋯ menu.
|
|
305
|
+
- **Self-contained** — no external picker/emoji CDN; everything ships in-file
|
|
306
|
+
(CLAUDE.md prototype rules).
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26">
|
|
2
|
+
<!-- The cursor for placing a comment: a white speech bubble outlined in black, its tail
|
|
3
|
+
pointing at the exact spot the pin will land (hotspot 4 23, the tail's tip). Flat by
|
|
4
|
+
design — cursor images are painted by the OS, where filters and shadows are unreliable. -->
|
|
5
|
+
<path d="M7 3 H20 A3 3 0 0 1 23 6 V15 A3 3 0 0 1 20 18 H10.5 L4 23 L6.5 18 A3 3 0 0 1 4 15 V6 A3 3 0 0 1 7 3 Z"
|
|
6
|
+
fill="#ffffff" stroke="#1a1a1a" stroke-width="1.8" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|