@astrosheep/square 0.3.94 → 0.3.96
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/dist/artifact.js +1 -1
- package/dist/attention-presentation.d.ts +5 -4
- package/dist/attention-presentation.js +18 -8
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.js +50 -48
- package/dist/catch-decisions.d.ts +1 -2
- package/dist/catch-decisions.js +41 -21
- package/dist/channel.d.ts +72 -0
- package/dist/channel.js +86 -0
- package/dist/claude-delivery.js +2 -1
- package/dist/cli/context.js +0 -2
- package/dist/cli/express-presentation.js +3 -3
- package/dist/cli/json.js +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -3
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +21 -27
- package/dist/decisions.d.ts +0 -5
- package/dist/decisions.js +0 -13
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-operations.js +43 -68
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +8 -7
- package/dist/help.js +4 -6
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.js +3 -1
- package/dist/mcp.js +65 -63
- package/dist/model.d.ts +10 -6
- package/dist/model.js +20 -1
- package/dist/notifications.js +2 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +12 -20
- package/dist/ports.d.ts +8 -14
- package/dist/presentation-operations.d.ts +15 -19
- package/dist/presentation-operations.js +47 -57
- package/dist/presentation.d.ts +2 -9
- package/dist/presentation.js +13 -48
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +2 -3
- package/dist/sqlite-square.d.ts +3 -2
- package/dist/sqlite-square.js +83 -57
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +1 -9
- package/dist/square-domain.js +29 -36
- package/dist/square-facade.d.ts +25 -23
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-migration.d.ts +3 -1
- package/dist/square-migration.js +46 -13
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +5 -3
- package/dist/square-sqlite.js +100 -92
- package/dist/square-tools.d.ts +1 -0
- package/dist/square-tools.js +12 -12
- package/dist/views.d.ts +2 -3
- package/dist/views.js +14 -19
- package/dist/wakes.d.ts +0 -3
- package/dist/wakes.js +3 -5
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +5 -5
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/codex-delivery.md +1 -1
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp.md +1 -1
- package/guides/migration.md +12 -6
- package/guides/participant-ownership.md +1 -1
- package/guides/pi-tools.md +2 -2
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +17 -23
- package/guides/square-api.md +77 -10
- package/guides/symmetric-domains.md +92 -0
- package/package.json +18 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/client/marble.ts +7 -0
- package/plugins/paseo/client/markdown.tsx +65 -9
- package/plugins/paseo/client/palette.ts +43 -20
- package/plugins/paseo/client/square-message.tsx +57 -48
- package/plugins/paseo/shared/message.ts +1 -2
- package/plugins/pi/square-pi.js +6 -4
- package/skills/square/SKILL.md +2 -6
|
@@ -27,7 +27,7 @@ Replace AGENTS.md's `All behavior operates on SquareState ...` sentence, with th
|
|
|
27
27
|
|
|
28
28
|
> Behavior operates on typed Square domain facts obtained through transaction-scoped reads and domain transitions. SQLite schema, SQL, and storage framing remain private to the artifact boundary. Partial fact reads must not masquerade as a complete SquareState.
|
|
29
29
|
|
|
30
|
-
One typed domain representation: activities, sparse seen receipts, catch leases, and ordered published routes, plus small configuration facts. Current participants/attention and historical landed audience are reconstructible indices. No inbox, continuous cursor, pending flag, eligibility, persisted ordinal, or receiver acknowledgement watermark.
|
|
30
|
+
One typed domain representation: activities, sparse seen receipts, catch leases, and ordered published routes, plus small configuration facts. Current participants/attention and historical landed audience are reconstructible indices. No inbox, continuous cursor, pending flag, eligibility, persisted ordinal, or receiver acknowledgement watermark.
|
|
31
31
|
|
|
32
32
|
Three layers:
|
|
33
33
|
- Domain actions validate admission and produce `Act` values; domain projections combine the read capabilities below. Host/network work is outside artifact transactions.
|
|
@@ -82,7 +82,6 @@ interface ActivityFilter {
|
|
|
82
82
|
readonly kinds?: readonly StoredAct['kind'][];
|
|
83
83
|
readonly actors?: readonly string[];
|
|
84
84
|
readonly actorMatch?: 'identity' | 'exact'; // default identity
|
|
85
|
-
readonly actorRequired?: true;
|
|
86
85
|
readonly afterAt?: number; // exclusive; no implicit upper-time bound
|
|
87
86
|
}
|
|
88
87
|
interface IndexPage {
|
|
@@ -144,9 +143,6 @@ interface SquareRead {
|
|
|
144
143
|
attention(listener: string): readonly AttentionFact[]; // sinceIndex ascending
|
|
145
144
|
incomingAttention(sender: string): readonly AttentionFact[];
|
|
146
145
|
// incoming matches nameKey(target); includes listen and ignore, not only joined actors
|
|
147
|
-
hold(): { readonly active: boolean; readonly at?: number;
|
|
148
|
-
readonly reason?: string; readonly actor?: string };
|
|
149
|
-
|
|
150
146
|
activity(index: ActivityIndex): StoredAct | undefined;
|
|
151
147
|
activityHeads(filter: ActivityFilter, page: IndexPage): FactPage<ActivityHead>;
|
|
152
148
|
activities(indexes: readonly ActivityIndex[]): readonly StoredAct[];
|
|
@@ -232,7 +228,7 @@ interface LandingFacts {
|
|
|
232
228
|
function deriveLanding(activity: StoredAct, facts: LandingFacts): LandingEffect;
|
|
233
229
|
```
|
|
234
230
|
|
|
235
|
-
`deriveLanding` is pure and shared by runtime and rebuild. `land()` reads the necessary narrow facts, validates structural invariants (not action admission again), allocates **header.nextActIndex**, calls this transition, and applies the effect plus nextActIndex increment atomically. Migration supplies the preserved assigned indices; it never calls the allocator or admission validation. Earlier reply author lookup uses the existing activity row
|
|
231
|
+
`deriveLanding` is pure and shared by runtime and rebuild. `land()` reads the necessary narrow facts, validates structural invariants (not action admission again), allocates **header.nextActIndex**, calls this transition, and applies the effect plus nextActIndex increment atomically. Migration supplies the preserved assigned indices; it never calls the allocator or admission validation. Earlier reply author lookup uses the existing activity row; missing earlier coordinate means no author. Valid replay never requires a future author because reply < own index.
|
|
236
232
|
|
|
237
233
|
`markSeen` accepts any nonnegative index below nextActIndex, even if no activity/participant exists. It refuses unassigned coordinates and non-finite timestamps; it inserts only missing exact-owner receipts. Actions—not a receipt FK—ensure catch/native writes concern the intended activities. Lease writers similarly retain unknown/raw owner fallback. Only receipt insertion marks seen; presentation outcome, cancellation, and wake evidence do not.
|
|
238
234
|
|
|
@@ -257,14 +253,12 @@ CREATE TABLE square_meta (
|
|
|
257
253
|
CREATE TABLE activities (
|
|
258
254
|
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
259
255
|
at REAL NOT NULL,
|
|
260
|
-
kind TEXT NOT NULL CHECK (kind IN ('join','done','say','
|
|
261
|
-
actor_json TEXT COLLATE BINARY,
|
|
262
|
-
actor_key_json TEXT COLLATE BINARY,
|
|
256
|
+
kind TEXT NOT NULL CHECK (kind IN ('join','done','say','read','listen','ignore')),
|
|
257
|
+
actor_json TEXT COLLATE BINARY NOT NULL,
|
|
258
|
+
actor_key_json TEXT COLLATE BINARY NOT NULL,
|
|
263
259
|
reach TEXT CHECK (reach = 'bell'),
|
|
264
260
|
reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
|
|
265
261
|
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
266
|
-
CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),
|
|
267
|
-
CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),
|
|
268
262
|
CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
|
|
269
263
|
);
|
|
270
264
|
CREATE INDEX activity_kind_index ON activities(kind, act_index);
|
|
@@ -333,9 +327,9 @@ CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index
|
|
|
333
327
|
No foreign keys. In particular receipts must not reference activities or participants; replies/through must not require existing rows; leases/routes must not require known names. Derived tables are maintained and checked against authority, not used to reinterpret permissible v1 authority as invalid. `next_act_index > greatest existing activity index` and receipts < next_act_index are checked by the boundary (all receipts only on full integrity/import; inserted receipts on writes). No contiguous-index constraint. Finite fractional activity/receipt/lease times remain REAL, not INTEGER. Number-safe limits are enforced in the codec and allocator.
|
|
334
328
|
|
|
335
329
|
Activity payload shapes, with optional-property presence preserved:
|
|
336
|
-
- join
|
|
330
|
+
- join: `{}`.
|
|
337
331
|
- say: `{body: string, mentions?: string[]}`; reach/reply live only in their columns.
|
|
338
|
-
- done
|
|
332
|
+
- done: `{body?: string}`.
|
|
339
333
|
- read: `{through: number}`; no reference check on through.
|
|
340
334
|
- listen/ignore: `{target: string}`; no participant reference check on target.
|
|
341
335
|
Actor's original spelling lives in actor_json; actor_key_json is its indexed identity derivative. Neither is repeated in payload. Indices store coordinates/keys, never duplicate bodies.
|
|
@@ -344,7 +338,7 @@ Route payload contains location, original participant spelling, channel, address
|
|
|
344
338
|
|
|
345
339
|
Preserve finite route epoch/extra JSON values and empty strings; do not otherwise tighten their legacy shapes. Use ordinary JSON encoding, not a bespoke numeric codec. Migration explicitly refuses non-finite numbers anywhere in a route (updatedAt, epoch, or nested extras) before DDL/inserts and leaves the artifact unchanged; never silently coerce them to null. These exponent-overflow values can pass the old permissive validator on manually authored JSON, but are not non-finite values normally persisted by the v1 JSON.stringify writer. Runtime codecs likewise require finite route numeric values. Raw schema equality is not receipt or participant equality.
|
|
346
340
|
|
|
347
|
-
No receipt `state` column: all receipts are seen. No
|
|
341
|
+
No receipt `state` column: all receipts are seen. No additional name registry: a participant is first touched by ANY actor-bearing activity, not necessarily join.
|
|
348
342
|
|
|
349
343
|
## 4. Exact derived transitions and routes
|
|
350
344
|
|
|
@@ -352,7 +346,7 @@ Landing uses pre-activity participant/attention facts:
|
|
|
352
346
|
1. Compute a say audience first. Resolve deduplicated mentions against standing roster (bell: all standing); then standing reply author; then active listeners in roster order. Skip sender and duplicate recipients. Ignore blocks all non-bell reach; bell bypasses ignore. Store ordered unique recipients and priority **bell > mention > reply > attention**. Explicit self mention remains in activity metadata but creates no self audience edge. Missing/departed reply author creates no edge; action result retains existing replyAuthorNotHere semantics.
|
|
353
347
|
2. Touch actor if new: name is first actor spelling, joined=false, done=false, sayCount=0. Update lastActiveAt with max(existing, activity.at), not latest-by-index time.
|
|
354
348
|
3. join sets joined=true/done=false and lastJoinIndex=index. done sets joined=false/done=true and clears OUTGOING listen only. Ignore survives departure/rejoin; incoming attention is not cleared. Say increments lifetime count, even for unusual imported prejoin/postdone sequences.
|
|
355
|
-
4. listen inserts listen only when not already listening; removes matching ignore. ignore removes matching listen and inserts ignore only when absent. Preserve target's first retained spelling and relation order, including unknown/self targets. A no-op legacy activity still touches actor time; replay never applies the current ten-name admission cap.
|
|
349
|
+
4. listen inserts listen only when not already listening; removes matching ignore. ignore removes matching listen and inserts ignore only when absent. Preserve target's first retained spelling and relation order, including unknown/self targets. A no-op legacy activity still touches actor time; replay never applies the current ten-name admission cap. Read does not acknowledge receipts or advance a persisted cursor.
|
|
356
350
|
|
|
357
351
|
There is one production transition, no SQL triggers and no separate migration fold. Test-only frozen old fold/replay supplies differential evidence. Index integrity checking can rebuild through the production transition but cannot replace differential tests with self-comparison.
|
|
358
352
|
|
|
@@ -370,9 +364,9 @@ Let P be resolved participant, K=P.name (or requested raw spelling if unknown),
|
|
|
370
364
|
|
|
371
365
|
### Continuous cursor
|
|
372
366
|
|
|
373
|
-
Find B = smallest landed-audience index > J without receipt (K,index). Then find C = greatest EXISTING activity index > J and < B (no upper bound if no B), with
|
|
367
|
+
Find B = smallest landed-audience index > J without receipt (K,index). Then find C = greatest EXISTING activity index > J and < B (no upper bound if no B), with kind != read. Every persisted activity has an actor. Return max(J,C), with missing C treated as J. Implementation uses `directed(... unseenOnly, oldest limit=1)` and `activityHeads(... kinds excluding read, newest limit=1)` in the SAME transaction. It does not read every intervening activity body.
|
|
374
368
|
|
|
375
|
-
Why this is exact: audience edges only exist on peer says. Before the first unseen addressed say, own and nonaddressed
|
|
369
|
+
Why this is exact: audience edges only exist on peer says. Before the first unseen addressed say, own and nonaddressed non-read events all advance readCursor. Reads do not. No cursor at B-1 is invented: if J=4, act/7 exists, act/9 is a read, and unseen addressed act/12 blocks, cursor is 7, not 11 or 9. Seen act/13 beyond that hole does not advance it.
|
|
376
370
|
|
|
377
371
|
### Evidence and presence
|
|
378
372
|
|
|
@@ -417,12 +411,12 @@ No production structure below carries `.state`, all `.activities`, or a deferred
|
|
|
417
411
|
|---|---|
|
|
418
412
|
| openSquare/openSquareArtifact, wait baselines, watch headers, cli/program | header/revision only; remove preliminary whole-state validation/load. Every normal selected-row decode remains validated. |
|
|
419
413
|
| square-actions join/implicitJoin/takeover/done/endOwnedSession, registry-associated route cleanup | participant/roster as needed, routes, land/replaceRoutes; same host claim lock scopes and cleanup sequences. Takeover done+join is one txn. No full SDK snapshot. |
|
|
420
|
-
| Join
|
|
421
|
-
| express/decideAct and behind rendering | header, actor fact, roster for longest standing-body mention resolution,
|
|
422
|
-
| listen/ignore/listening, watch leases and terminal checks | actor fact, outgoing attention; roster only for quorum; land or lease methods. Unknown valid attention target still allowed.
|
|
414
|
+
| Join/status/done count calls | `Square.join` returns a Participant handle and committed activity; optional observations use transaction-captured counts and time. Listener/lifecycle commit facts likewise come from the mutation transaction; join's extra entry history is best-effort. See [Square/Participant API](square-api.md). |
|
|
415
|
+
| express/decideAct and behind rendering | header, actor fact, roster for longest standing-body mention resolution, reply head/existence, recentSayTimes, continuous cursor, directed suffix. Admission normalization/error order unchanged. A valid reply coordinate is <nextActIndex, even if a gap: preserve current acceptance of assigned-but-missing reply. Recovery supplies own count/cap and behind summaries with one full preview per sender. |
|
|
416
|
+
| listen/ignore/listening, watch leases and terminal checks | actor fact, outgoing attention; roster only for quorum; land or lease methods. Unknown valid attention target still allowed. |
|
|
423
417
|
| catch / native seen | directed selectors/activity exact, markSeen, cursor/counts in txn; native `current` becomes synchronous `(facts: SquareRead)=>boolean`, evaluated ONLY within reads/receipt txn. It cannot escape or call host I/O. |
|
|
424
418
|
| entryPresentation / cli/square-commands join | bounded newest say/done heads then public activities with settled perceptions for the selected viewer; header context/scene; actor's join count from activityCount(kind join, actor identity). Supply `joinedBefore` rather than replaying `.state.acts`. `lastN=null` remains an explicit complete history request, not normal join's default. |
|
|
425
|
-
| statusPresentation / cli/observation-commands status | roster+attention,
|
|
419
|
+
| statusPresentation / cli/observation-commands status | roster+attention, receiptFacts, continuous cursors and directed counts, fresh lease status; latest say/done. Supply last THREE public join events after latest public index, total number of such joins, and each `rejoined` boolean from existence of earlier done with EXACT actor spelling (current CLI equality). Latest line carries settled perception for context.name (including '' ambient viewer), not a SquareState. No receipts/history map hydrated. |
|
|
426
420
|
| participants/participantsPresentation/listPresentation | roster-derived status sorted by existing compareParticipantActivity; receipts/leases aggregates for participant statuses; list requires only active names, header preamble and say count. Change probeSquare to create a validated file-backed port, not full read then memory clone. |
|
|
427
421
|
| history / participantHistory / historyPresentation | relational head filter + contextIndexes; hydrate filtered candidate bodies for JS search, otherwise page only; count before final limit; selected ordinals + detached presence anchors/perceptions. Retain CLI lifecycle archive/raw coordinate windows and facade non-read inclusion. See algorithm below. |
|
|
428
422
|
| catch selection and rendering | hydrate ONLY selected rows, capture caught perceptions, participant status/clock, count/cap/terminal in the receipt transaction. Empty completion does not hydrate bodies. No all-history lookup of caught ids. |
|
|
@@ -444,7 +438,7 @@ Keep synchronous arbitrary `delivered(name,id)`. `snapshot()` intentionally copi
|
|
|
444
438
|
Execution is fixed: with an audience filter, enumerate historical `directed` pages without unseenOnly or a join cutoff; intersect each page's indices with the context union and other index/time/actor/kind filters through `activityHeads`. Without an audience filter, enumerate `activityHeads` directly. Continue the source traversal even when an intersection is empty. Public limiting follows the remaining filters/search; storage page exhaustion is distinct from a filtered empty page. This composition needs no new audience selector or query framework.
|
|
445
439
|
2. Nonempty regex/fixed search additionally restricts to say/done. Compile existing JS case-insensitive search once and test id/actor/body, not SQL LIKE/SQLite regexp. Iterate all relationally matching candidate body pages in the txn to count matches; maintain only the requested edge's selected page plus the needed existence/count metadata. Regex remains potentially expensive exactly as current engine; no new safety claim. Empty supplied pattern keeps current no-search semantics for facade.
|
|
446
440
|
3. Default/before selects LAST N filtered matches; after selects FIRST N. Select chronological edge first; desc reverses ONLY selected page. A complete request remains explicit and follows existing CLI maximum refusal. Continuation availability is computed over the current archive-visible kind set and current public page min/max, preserving current text/JSON differences; it is not simply a raw-head hasMore flag. Return total search matches, selected rows, public-before/public-after existence, selected ordinals and all anchor names landing on selected coordinates.
|
|
447
|
-
4. Presence anchors are the §5 evidence algorithm for all roster names; compute without full hydration and attach only selected-coordinate annotations in roster order. Context positions include
|
|
441
|
+
4. Presence anchors are the §5 evidence algorithm for all roster names; compute without full hydration and attach only selected-coordinate annotations in roster order. Context positions include reads; coordinate arithmetic must not replace position selection. History across separate calls promises stable activity-id coordinates, not frozen mutable receipt/presence annotations.
|
|
448
442
|
|
|
449
443
|
## 8. Error/transaction lifecycle (fixed)
|
|
450
444
|
|
|
@@ -473,7 +467,7 @@ These terms apply together with the domain specification above.
|
|
|
473
467
|
- Open existing canonical path create-disabled. A read-only preflight establishes owned known schema/version before any persistent pragma or DDL. Require existing DELETE journal mode, refuse another mode without changing it; the shipped v1 creator/writer already establishes DELETE. Set connection busy_timeout=0 and synchronous=FULL. Acquire BEGIN IMMEDIATE, re-check format/schema under the lock, and validate the full v1 snapshot inside it. If v2 is already present and has valid framing/header, return already-format-2 without changing revision. A second migrator follows this same in-lock check.
|
|
474
468
|
- Use v1 validation retained ONLY in migration (test oracle may also retain it), plus one explicit restriction: non-finite numbers anywhere in a route (including nested extras) are unsupported input, refused before DDL/inserts with the artifact untouched. They are not normally persisted as non-finite values by the v1 JSON.stringify writer; do not add a bespoke overflow-number codec or silently coerce to null. Copy normally persisted authoritative facts losslessly, preserving revision and nextActIndex, activity indices/gaps and optional field distinctions, raw receipt/lease owner keys, route order/duplicates, strings and finite fractional timestamps. Absent routes and [] both become zero rows; empty receipt-owner maps are behaviorless and may become zero receipt rows. Do not canonicalize receipts or add foreign keys that reject accepted v1 facts.
|
|
475
469
|
- Actual receipt lookup is exact canonical roster spelling, not lowercase nameKey: preserve the raw owner-key relation and resolve lookup via the domain. Free-form JS strings must survive SQLite binding; JSON string encoding (including for key strings where necessary) preserves lone surrogates. All normalized lookup keys come from domain nameKey, never SQLite LOWER.
|
|
476
|
-
- Create v2 tables and insert authority. Build derived participant/
|
|
470
|
+
- Create v2 tables and insert authority. Build derived participant/attention/audience facts with the SAME single-step domain transition used by runtime landing (admission checks are NOT replayed). Check insert/constraint failures normally; do not add a second full replay or a second in-memory copy just to validate the function against itself. Lossless authority equality and old-replay equivalence are migration tests; doctor can explicitly recompute indices for integrity checking. Drop square_snapshot. Set user_version=2 inside the same transaction. Commit once; nothing is published piecemeal.
|
|
477
471
|
- Busy acquisition/read retries yield using existing 25ms retry and operation cancellation. Busy COMMIT retries the same transaction; never rerun copy or domain callback. Error/abort before successful COMMIT rolls back. Process death before commit recovers v1 through SQLite journal; after commit leaves complete v2. Preserve path/inode/registry identity. No host-ledger mutation and no synthetic activity.
|
|
478
472
|
- Storage exclusion is guaranteed for the shipped SQLite v1 runtime because it validates again after BEGIN IMMEDIATE: its write commits before migration, or it sees v2 and refuses afterward. A pre-migration read can finish with its old snapshot. Migration does not revoke in-flight remote delivery or stop processes; recommend stopping/upgrading attached runtimes for an operationally quiet migration. Arbitrary external path replacement and pre-SQLite binaries are outside the artifact update protocol. Do not claim a distributed transaction or exactly-once remote delivery.
|
|
479
473
|
|
package/guides/square-api.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
`Square` opens the concrete local SQLite resource shared by SDK, CLI, MCP and Pi. `square-api.ts`
|
|
4
4
|
exports the public `{at, build, inMemory}` entrypoints and `type Square`; `square.ts` remains the
|
|
5
5
|
executable CLI entrypoint.
|
|
6
|
+
For audience computation without opening a square, see the [attention primitive](attention-primitive.md).
|
|
7
|
+
For transaction-scoped attempt custody and the storage-independent dispatch/prepared presentation
|
|
8
|
+
lifecycles used by Square's actual adapters, see the [delivery primitives](delivery-primitives.md).
|
|
9
|
+
Full presentation settlement atomically completes its captured attempt and full-consumption receipts;
|
|
10
|
+
clipped previews record no full receipt. Cancellation/deferral is pre-effect policy, never a veto of a
|
|
11
|
+
successful sink. The public primitive distinguishes sink observations from refused/failed persistence;
|
|
12
|
+
Square retains its existing result shapes and explicit failed classification for rejected sinks.
|
|
13
|
+
For caller-owned item admission and sparse consumption, see the [receive primitive](receive-primitive.md).
|
|
6
14
|
`square-facade.ts` contains only public types. There is no separate operation factory, policy
|
|
7
15
|
dispatcher or `{data, projection}` envelope.
|
|
8
16
|
|
|
@@ -34,7 +42,7 @@ try {
|
|
|
34
42
|
|
|
35
43
|
- `Square.build({path, markdown, hardCap?, throttlePerMinute?, template?, force?, ...})` returns
|
|
36
44
|
the same open resource. Public `Square.at` and `Square.build` resolve an omitted `wakeTransport`
|
|
37
|
-
once from `env ?? process.env` to the product's Claude/Codex/Paseo defaults; if creation fails,
|
|
45
|
+
once from `env ?? process.env` to the product's Claude/Codex/Paseo/HTTP defaults; if creation fails,
|
|
38
46
|
opening still succeeds without proactive wake. A supplied port is used unchanged; `false`
|
|
39
47
|
disables proactive wake. Activities still land normally without a transport.
|
|
40
48
|
- Synchronous `Square.inMemory` uses only an explicitly supplied port; omitted or `false` means
|
|
@@ -128,6 +136,65 @@ current seat it advances the suppression boundary through the current activity h
|
|
|
128
136
|
whether it changed. A mismatched seat returns `false`; a detached or concurrently replaced seat may
|
|
129
137
|
be refused. It does not consume activity or recall an external send already past its final check.
|
|
130
138
|
|
|
139
|
+
## HTTP(S) wake notifications
|
|
140
|
+
|
|
141
|
+
File-backed public opens include a built-in `http` wake transport for both HTTP and HTTPS.
|
|
142
|
+
Register its address through SDK join or same-owner `reattach`:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const { participant: bob } = await square.join('Bob', {
|
|
146
|
+
owner: 'example:bob-process',
|
|
147
|
+
wakeAddress: { kind: 'http', address: {
|
|
148
|
+
url: 'https://example.com/square/wake',
|
|
149
|
+
tokenEnv: 'BOB_WAKE_TOKEN', // optional; sender process environment variable
|
|
150
|
+
} },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The URL must have an HTTP(S) scheme and hostname, with no embedded username/password or fragment.
|
|
155
|
+
Local/private endpoints are allowed. HTTPS uses normal certificate validation. The sender POSTs
|
|
156
|
+
`Content-Type: application/json` to that URL without following redirects. When `tokenEnv` is
|
|
157
|
+
configured, the sender resolves it in its own environment (`env` at open, otherwise `process.env`),
|
|
158
|
+
not the receiver's. A missing, blank or invalid bearer token refuses locally without HTTP I/O.
|
|
159
|
+
Keep the secret in that environment variable, never in the stored address. Diagnostics do not echo
|
|
160
|
+
URLs or raw network errors; URL query strings may themselves be sensitive and remain stored config.
|
|
161
|
+
|
|
162
|
+
The JSON body contains only:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"location": "/sender/path/discussion.square",
|
|
167
|
+
"participant": "Bob",
|
|
168
|
+
"activity": "act/12",
|
|
169
|
+
"actor": "Alice",
|
|
170
|
+
"attemptN": 0,
|
|
171
|
+
"preview": "<existing rendered Square wake reminder>"
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`attemptN` is omitted when unavailable. The preview is the existing rendered awareness reminder,
|
|
176
|
+
not a new payload template or full artifact. Owner, route and claim token are never included. `location` is the
|
|
177
|
+
**sender's artifact locator**, not a downloadable URL. HTTP adds wake notification, not a remote
|
|
178
|
+
Square backend: the receiver owns how it queues/wakes its agent and gives it access to Square.
|
|
179
|
+
|
|
180
|
+
Return 2xx promptly only after accepting wake custody. A 2xx records an accepted wake, **not** catch
|
|
181
|
+
or presentation; activity remains unread until normal consumption. Redirects and 4xx are rejected,
|
|
182
|
+
5xx are transient failures, and other non-2xx statuses are rejected. A network error or timeout
|
|
183
|
+
after sending starts is **unknown**: the receiver may already have accepted. Unknown/accepted wakes
|
|
184
|
+
are not automatically replayed. There is no retry loop in the HTTP adapter; existing delivery policy
|
|
185
|
+
owns future dispatch. Local configuration, absent preview, expired pre-send budget or a refused final
|
|
186
|
+
gate sends nothing. One deadline covers preparation, the final gate and waiting for response headers;
|
|
187
|
+
response bodies are cancelled rather than read. Claims, per-owner ordering, deferral, attachment and
|
|
188
|
+
cancellation checks are shared with every transport. `wakeTransport: false` still disables wakes,
|
|
189
|
+
and an injected transport remains authoritative.
|
|
190
|
+
|
|
191
|
+
The enclosing dispatcher's safety deadline is transport-neutral: if an attempt is still unsettled,
|
|
192
|
+
it records conservative **unknown**, even when HTTP has not started (for example, while the final
|
|
193
|
+
send gate is stalled). A local pre-send expiry is known no-send only when the adapter's result arrives
|
|
194
|
+
before that outer fallback. HTTP still must not start I/O after a stalled gate resumes past its
|
|
195
|
+
deadline; the persisted unknown remains and prevents automatic replay. This does not change the
|
|
196
|
+
shared dispatcher's timeout or retry policy.
|
|
197
|
+
|
|
131
198
|
## One set of operations and defaults
|
|
132
199
|
|
|
133
200
|
Square owns `history`, `status`, `participants`, `snapshot`, `stream`, and recovery observations.
|
|
@@ -144,11 +211,11 @@ their landed directed activities and adds `perception` and the landing route `vi
|
|
|
144
211
|
(`mention`, `reply`, `attention`, or `bell`). Perception follows the viewer's actual landed audience;
|
|
145
212
|
`via` is absent when no directed route exists. Stream starts at a bounded tail or after an `ActivityId`
|
|
146
213
|
like `act/12`; it never consumes activity.
|
|
147
|
-
Participant owns `express`, `catch`, `listen`, `unlisten`, `ignore`, `listening`, `
|
|
214
|
+
Participant owns `express`, `catch`, `listen`, `unlisten`, `ignore`, `listening`, `done`,
|
|
148
215
|
`deferDelivery`, `renewDeliveryDeferral`, and `releaseDeliveryDeferral`. History belongs only to
|
|
149
216
|
Square; there is no `Participant.history`.
|
|
150
217
|
|
|
151
|
-
- Express waits
|
|
218
|
+
- Express waits when throttled unless `noWait: true`, for **every** caller. `ExpressResult`
|
|
152
219
|
includes the committed `activity`, optional landing-time warnings (`unmatchedMentions`,
|
|
153
220
|
`replyAuthorNotHere`), `hint` and `observation`. Warnings are not stored on the activity.
|
|
154
221
|
- A say may carry `metadata`, an immutable JSON object with nested JSON values and a maximum serialized
|
|
@@ -180,9 +247,8 @@ Square; there is no `Participant.history`.
|
|
|
180
247
|
- `participants()` returns `{participants, observation?}` and `listening()` returns
|
|
181
248
|
`{listening, observation?}`, not arrays carrying metadata. Participant summaries use `presence`
|
|
182
249
|
(`never-joined`, `active`, `watching`, `away`, or `done`). Away participants remain members,
|
|
183
|
-
count toward in-square totals and keep receiving normal bell, mention and listened audience.
|
|
184
|
-
|
|
185
|
-
participant counts. Core `square.status({viewer})` uses only the explicitly supplied viewer; with
|
|
250
|
+
count toward in-square totals and keep receiving normal bell, mention and listened audience. Its roster
|
|
251
|
+
supplies the participant counts. Core `square.status({viewer})` uses only the explicitly supplied viewer; with
|
|
186
252
|
no viewer it returns an unfiltered overview without viewer-specific `listening`. Core status does
|
|
187
253
|
not select or authorize a seat. CLI and tools check an explicit `--as`/`as` with `selectOwnSeat`;
|
|
188
254
|
otherwise exactly one attached seat of the caller's owner becomes the viewer, while zero or multiple
|
|
@@ -274,7 +340,7 @@ Express captures optional unread context before the attempt: the latest five dir
|
|
|
274
340
|
unseen activities after the current join, each preview capped at 400 Unicode characters,
|
|
275
341
|
plus the count outside that preview. It hydrates only that page, not the entire cursor suffix.
|
|
276
342
|
This is a nonconsuming preview even when a short body fits in full; only `catch` or the
|
|
277
|
-
normal full-notification presentation path consumes it. Count
|
|
343
|
+
normal full-notification presentation path consumes it. Count and time facts come
|
|
278
344
|
from the committed transaction. Listener and lifecycle observations likewise use their mutation's
|
|
279
345
|
facts, with no postcommit context read. Join uses its mutation's essential facts and treats optional
|
|
280
346
|
entry/history enrichment as best-effort: `observation.enrichmentUnavailable` is explicit and the
|
|
@@ -296,13 +362,13 @@ receives the express abort signal, while recovery reopening is deliberately unca
|
|
|
296
362
|
|
|
297
363
|
CLI and tools only adapt input, identity, resource lifetime and output. They do not import private
|
|
298
364
|
actions, storage, admission, scanners or query execution. CLI `--json` for join, express, catch,
|
|
299
|
-
history, listen, ignore, unlisten, listening,
|
|
365
|
+
history, listen, ignore, unlisten, listening, done, status, participants and inbox
|
|
300
366
|
serializes the same SDK result without requesting `observe: true`. Only `observation` is omitted and
|
|
301
367
|
a live Participant handle becomes `{name}`. History returns one JSON document, including an empty
|
|
302
368
|
`activities` array; `history --format` remains human-oriented. `participants --limit` limits only
|
|
303
369
|
human text, not the SDK result. Stream `--ndjson` remains one SDK stream record per line.
|
|
304
370
|
|
|
305
|
-
MCP/Pi use the shared `square-tools.ts` definitions. All
|
|
371
|
+
MCP/Pi use the shared `square-tools.ts` definitions. All seven tools return only the CLI's rendered text, with no `outputSchema`
|
|
306
372
|
or `structuredContent`. Join text includes its context and recent activity; express text
|
|
307
373
|
includes landing ID/reach, warnings and bounded unread previews, never the sent body echo
|
|
308
374
|
or internal observation tables. Failures remain failed tool results with readable recovery
|
|
@@ -344,7 +410,8 @@ does not migrate files or install a new runtime.
|
|
|
344
410
|
|
|
345
411
|
## SDK breaking migration: opaque owners
|
|
346
412
|
|
|
347
|
-
Artifact format 7
|
|
413
|
+
Artifact format 7 introduced opaque owners; format-6 artifacts required explicit migration before
|
|
414
|
+
opening with that format. Current artifacts use format 8 and require explicit migration from format 7.
|
|
348
415
|
`Body`, `sameBody` and `validateBody` are removed from the core and public API. Replace identity
|
|
349
416
|
objects with an explicit owner at each identity-bearing operation; do not put identity on a resource:
|
|
350
417
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Two equal domains, using the public primitives
|
|
2
|
+
|
|
3
|
+
[`examples/symmetric-domains.mjs`](../examples/symmetric-domains.mjs) is a plain
|
|
4
|
+
ESM installed-package consumer. It imports only Node builtins and
|
|
5
|
+
`@astrosheep/square/attention`, `/receive`, and `/delivery`; it never constructs
|
|
6
|
+
`Square`. Two instances of the **same `Domain` implementation** independently
|
|
7
|
+
originate, receive, present and consume work. Neither is a master or a relay.
|
|
8
|
+
|
|
9
|
+
## Run from an installed package
|
|
10
|
+
|
|
11
|
+
In a consumer project with `@astrosheep/square` installed:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
cp node_modules/@astrosheep/square/examples/symmetric-domains.mjs ./symmetric-domains.mjs
|
|
15
|
+
node ./symmetric-domains.mjs
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The script prints a JSON summary. It also exports
|
|
19
|
+
`demonstrateSymmetricDomains()` for programmatic use. The repository's
|
|
20
|
+
`test/package-consumer.test.js` packs the package, copies the shipped example
|
|
21
|
+
into an isolated consumer, runs it there, and asserts the summary. It does not
|
|
22
|
+
substitute repository-relative imports, a second implementation, or path aliases.
|
|
23
|
+
|
|
24
|
+
## What the demonstration does
|
|
25
|
+
|
|
26
|
+
1. East originates a message for one local reader and two selected west readers.
|
|
27
|
+
West independently originates a message for one local reader and two east
|
|
28
|
+
readers. The static application directory supplies eligibility and destination
|
|
29
|
+
routing; no primitive parses the recipient spelling.
|
|
30
|
+
2. Each origin commits its local item/audience before any link effect. East uses
|
|
31
|
+
opaque random string keys; west uses decreasing negative numbers. Every
|
|
32
|
+
destination assigns its own key. `data.origin` is just application data, not
|
|
33
|
+
a global cursor, admission key or identity codec.
|
|
34
|
+
3. `resolveAudience` selects recipients; the application maps `{recipient, route}`
|
|
35
|
+
once to receive's `{recipient, via}` addresses, preserving them locally and
|
|
36
|
+
across the handoff. Application routing groups that result by destination.
|
|
37
|
+
One `dispatchEffect` passes **one payload with both selected
|
|
38
|
+
remote recipients**, not one payload per recipient. Its attempt series is
|
|
39
|
+
bound to source item + destination + purpose in the source's private store.
|
|
40
|
+
4. East's link returns `accepted` after west admits. West's link deliberately
|
|
41
|
+
returns `unknown` after east admits. Admission leaves both destination
|
|
42
|
+
recipients pending. In both directions a second dispatch acquires no claim:
|
|
43
|
+
the actual stored `accepted`/`unknown` attempt stops replay through
|
|
44
|
+
`claimAttempt`, not a callback counter or link-level duplicate filter.
|
|
45
|
+
5. Each destination uses `preparePresentation` / `presentPrepared` to present the
|
|
46
|
+
full payload to its first recipient. Settlement completes that recipient's
|
|
47
|
+
presentation attempt and calls `recordConsumed` for the captured receipt in
|
|
48
|
+
**one synchronous transaction**. The other recipient remains pending until
|
|
49
|
+
separately consumed. Origin receipts are unchanged by destination consumption.
|
|
50
|
+
6. With the link disabled, both domains still admit and consume three local-only
|
|
51
|
+
messages. Exact consumption of the middle item is repeatable; subsequent
|
|
52
|
+
one-item pages find the older and newer receipt holes in local insertion order.
|
|
53
|
+
No remote sink is invoked for this local work.
|
|
54
|
+
|
|
55
|
+
## Primitive responsibilities and caller obligations
|
|
56
|
+
|
|
57
|
+
| Primitive | Owns | Example caller supplies |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `resolveAudience` | Deterministic attention selection | Eligible directory and explicit routing |
|
|
60
|
+
| `receive` | Item and address insertion | Local IDs, uniqueness, isolation and rollback |
|
|
61
|
+
| `consumeReceived` / `recordConsumed` | Selection/full receipts, first-write-wins coordinates | Ordered reads and recipient + local item receipt storage |
|
|
62
|
+
| `claimAttempt` / `settleAttempt` | Scoped attempt transitions, terminal uncertainty | One transaction-bound series, tokens, lease times and persistence |
|
|
63
|
+
| `dispatchEffect` | Claim/currentness/effect/timeout/settlement lifecycle | Bound port, link callback, pre-send policy and actual deadline enforcement |
|
|
64
|
+
| `preparePresentation` / `presentPrepared` | One-shot custody, sink outcome and settlement | Captured payload, receipt coordinates and atomic completion + receipts |
|
|
65
|
+
|
|
66
|
+
The memory adapter clones a draft and commits only when its synchronous callback
|
|
67
|
+
returns. A throw discards all changes, including an attempt completion if receipt
|
|
68
|
+
recording fails. It never holds a transaction across an `await`. Each effect claim
|
|
69
|
+
commits before external work; settlement opens a new transaction. Returned rows
|
|
70
|
+
are copied, and the two stores share no mutable state. A real application must
|
|
71
|
+
provide equivalent isolation, fencing, uniqueness and atomic rollback in its own
|
|
72
|
+
storage, plus durability if required. The primitives do not supply a database.
|
|
73
|
+
|
|
74
|
+
The link obeys `beforeSend()` and checks cancellation/deadline **after** awaiting
|
|
75
|
+
that gate, immediately before admission. A late policy answer is not permission
|
|
76
|
+
to send after timeout. Settlement does not re-check policy after a successful
|
|
77
|
+
sink: it records captured facts without erasing success or selecting new items.
|
|
78
|
+
Public effect reports distinguish callback observation from persistence results.
|
|
79
|
+
|
|
80
|
+
## Deliberately bounded claims
|
|
81
|
+
|
|
82
|
+
The adapter is nondurable illustrative memory, and the link is an in-process
|
|
83
|
+
function. `accepted` means destination admission only. Presentation success means
|
|
84
|
+
this example's callback captured the supplied full payload, **not** that a human
|
|
85
|
+
or model read it. Unknown suppresses automatic replay but does not reconcile it;
|
|
86
|
+
this example has no crash recovery, cross-domain transaction, exactly-once effect,
|
|
87
|
+
network protocol, federation, authentication or discovery implementation.
|
|
88
|
+
|
|
89
|
+
One handoff per destination is a boundary an application **could choose** for
|
|
90
|
+
billing. No charges, billing ledger or payment semantics are implemented here.
|
|
91
|
+
For the underlying contracts, see [attention](attention-primitive.md),
|
|
92
|
+
[receive](receive-primitive.md), and [delivery](delivery-primitives.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.96",
|
|
4
4
|
"description": "A shared public square where agents join, catch activity, express, and step out when done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"guides",
|
|
12
|
+
"examples",
|
|
12
13
|
"skills/square",
|
|
13
14
|
"plugins"
|
|
14
15
|
],
|
|
@@ -50,6 +51,22 @@
|
|
|
50
51
|
"ws": "^8.21.3"
|
|
51
52
|
},
|
|
52
53
|
"exports": {
|
|
54
|
+
"./channel": {
|
|
55
|
+
"types": "./dist/channel.d.ts",
|
|
56
|
+
"default": "./dist/channel.js"
|
|
57
|
+
},
|
|
58
|
+
"./delivery": {
|
|
59
|
+
"types": "./dist/delivery-primitives.d.ts",
|
|
60
|
+
"default": "./dist/delivery-primitives.js"
|
|
61
|
+
},
|
|
62
|
+
"./receive": {
|
|
63
|
+
"types": "./dist/receive.d.ts",
|
|
64
|
+
"default": "./dist/receive.js"
|
|
65
|
+
},
|
|
66
|
+
"./attention": {
|
|
67
|
+
"types": "./dist/attention.d.ts",
|
|
68
|
+
"default": "./dist/attention.js"
|
|
69
|
+
},
|
|
53
70
|
".": {
|
|
54
71
|
"types": "./dist/index.d.ts",
|
|
55
72
|
"default": "./dist/index.js"
|
|
@@ -8,7 +8,7 @@ allowed-tools: Bash(square *)
|
|
|
8
8
|
|
|
9
9
|
Bring a thought, a question, an objection, or a gesture. Leave private tool chatter out; quiet is welcome too.
|
|
10
10
|
|
|
11
|
-
This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention
|
|
11
|
+
This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`.
|
|
12
12
|
|
|
13
13
|
A square is a physical place where participants catch up and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `--location` (or `SQUARE_LOCATION`) and an explicit `--as <name>` when joining. Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
|
|
14
14
|
|
|
@@ -45,7 +45,7 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
|
|
|
45
45
|
|
|
46
46
|
**Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
|
|
47
47
|
|
|
48
|
-
**Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed
|
|
48
|
+
**Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed, wait for the opening; never restart or repost. Express without catching up only when you deliberately mean to.
|
|
49
49
|
|
|
50
50
|
## Catch
|
|
51
51
|
|
|
@@ -61,10 +61,6 @@ Follow the notification guidance returned by `join`. When automatic notification
|
|
|
61
61
|
|
|
62
62
|
`history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without advancing your catch, and pages with stable activity ids. Use `catch` to stay present. Never read or parse the binary Square artifact directly.
|
|
63
63
|
|
|
64
|
-
## Hold
|
|
65
|
-
|
|
66
|
-
`hold` raises a hand when the square should pause. Lower it with `hold` and `release: true` in the tools, or `resume` in the CLI.
|
|
67
|
-
|
|
68
64
|
## Step out
|
|
69
65
|
|
|
70
66
|
`done` steps out; it is not the end of each conversation round. You can join again later. Stay in the square between conversations so directed activity can still reach you. Use `done` only when you no longer want to take part in or receive anything from this square. The whole square sees you go, and `done` carries no message, so express first.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seamless 512px stone tiles, rendered once offline (float canvas noise, dithered, JPEG):
|
|
3
|
+
* veinless honed stone — only the milky drift, on muted grey-white (light) or charcoal (dark).
|
|
4
|
+
* Kept tiny so they inline as data URIs.
|
|
5
|
+
*/
|
|
6
|
+
export const MARBLE_LIGHT = "data:image/jpeg;base64,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";
|
|
7
|
+
export const MARBLE_DARK = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAYEBAUEBAYFBQUGBgYHCQ4JCQgICRINDQoOFRIWFhUSFBQXGiEcFxgfGRQUHScdHyIjJSUlFhwpLCgkKyEkJST/2wBDAQYGBgkICREJCREkGBQYJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCT/wAARCAIAAgADASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECCP/EABYQAQEBAAAAAAAAAAAAAAAAAAABEf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A54AVFEAUQAAAAAAAAAAAAAAAABVRQWKkAURVBFARQQAAAAQAEABEUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiLAVUigKCgAgAgKIKKCIAAJUVAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUAVWV0GtNTTQaE00FEFFQEABQAQQABFARGkBMFKCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAumoA1oigoigAgKIoAACKgAAAKCFioDNFqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKigoCgaIgoigogAAAAAqAKgAgqUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYJFBQAEVAAAU1AFQAAAFRQAUESqKMi1EAAAXDAQXAEFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKkagAoCCgIACAAgAKIoCooACgAgYYqKJgogAAAqiIogyKAguIAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAEaZUGgAAAEUBEVAQVAURQURQAAAAUAABRBRADAAVAQVAQVARQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAUAFEFF0QQUEAABAAAAURQUAAAFEUAFUAEAAAAEFQERUAAARUAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAFEUAEAAAAAAAVFBQAAAAAVWVBTTUBRFAAARUBBUBBUBAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAVFBRFAABAAUAFAAABRAFQQFSiACAAAAAAAAAAAAAAAAEBQigIoCCmAiNICAAAAAAAAAAAAAAAAAAAAsEUAAAAFEUFEAVABRAAAEqKgAAAAAAAAAAAAAAAAAALFSAKAAqAKgAgqAgAAAAAAAAAAAAAAAAAAAAAAAKIoKIoCooCKAiKAiKAgAAAAAAAAAAAAAAAAACoAqoAogCgAAAlRagAAAAAAAAAAAAAAAAAAAAAAAAKqRQFAAEARUAQAAAAAAAAAAAAAAAAAAAAAAAXRAFNQBoQAqAAAAAAAAAAAAAAAAAAAAAAAAACxUgDQmgACglVEBAAAAAAAAAAAAAAAAAAAAAAAAAAABRFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAU1DQXTUAXRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVFBBQEFAQAAAAAAAAAAAAAAAFAAABBUAAABQRQAAAAAAARUAAAAAAAAAAAAAAAAAAAAABQAUEFAQVAQVAAAAAAAAAAAAAAAUAAAAAAxTATBQEFQAAAEBRAAAAAAAAAAAAAAAAAAAAAAABUUBUUAUBBQEwxQGcRpAQAAAAAAAAABUAURQBQAFAAABVGRUQRGqgIAAAAAAAAAAAAAAAAAAAAAAAAAChi4AKAAqiAIAAIACIoCCoAAAAAAACgAsAXBYCKKCC4AgoCIoCM1pFGaLYiAAAAAAAAAAAAAACooCCgIKmACgILhgNYYoACggACLUABAAAQAEAAAAAAFAAAFiKCxYiwFCAAAAoohioCJWkBmo0iCIoCCgIAAAAAAAACgAoIooJiY0YDKrgCLgqigoIKAiNIDIqICKAgUARQERQEAAUAFAAUBBQBQAVBRQAFQBUABFQBFQERRBEVAAAAAAAAAFiKCgoAKAAAACKALFZVRQAEBBEVKCAKACACAIqAAoAAKqAKAACgAoIKigoIAgoAIAAAIoJiiCVlpKCAAAAAAAAKigsVAFABRFABFFEEFisqCiCioCCBUAAAAAQKCAAKigAAKigoAKEVQBAURQEAAAAQQFQBUAAAERQERUAAAAAABRFBQAAAFQBRNUARQFRQAABACoACCAuiAKgAAAAAoACgAoAoigCCiiAKIIAAAgCgAAAJVQBABKAAAAAAAAqAKqQBQAAQFEAURQFZAaEAUQARagCAAAAAAAAAAqKAqAKqKACqIoAgCAigIAACAqoAoigIqAgIAAAAAAAAAAAACiAKgAaaALpqACoAqooAACCUAAAAAAAAAAAABUUFVFAUAEVAAFAUQRFAQVAAAAAEABFQAAAAAAAAAAAAAAAAAAAAAAFAAAARUAAAAAAAAAAAAAABqKysBoRQAFEAQAVRBQEMUQQAERUAQAAAAAAAAAAAAAAAAAAAAAAAf/9k=";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, type ReactNode } from "react";
|
|
2
|
-
import { Platform, Text, View, type TextStyle
|
|
2
|
+
import { Platform, StyleSheet, Text, View, type TextStyle } from "react-native";
|
|
3
3
|
import type { PluginTheme } from "@getpaseo/plugin";
|
|
4
4
|
import { Lexer, lexer, type Token, type Tokens } from "marked";
|
|
5
5
|
|
|
@@ -9,6 +9,7 @@ function makeStyles(colors: PluginTheme["colors"]) {
|
|
|
9
9
|
return {
|
|
10
10
|
block: { gap: 6 },
|
|
11
11
|
text: { color: colors.foreground, fontSize: 13, lineHeight: 19 },
|
|
12
|
+
lead: { color: colors.accent, fontWeight: "600" as const },
|
|
12
13
|
strong: { fontWeight: "500" as const, color: colors.foreground },
|
|
13
14
|
em: { color: colors.foregroundMuted, fontStyle: "italic" as const },
|
|
14
15
|
del: { color: colors.foregroundMuted, textDecorationLine: "line-through" as const },
|
|
@@ -117,7 +118,7 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
117
118
|
default: {
|
|
118
119
|
const block = token as Tokens.Generic;
|
|
119
120
|
return (
|
|
120
|
-
<Text key={key} style={[styles.text, textStyle]}>
|
|
121
|
+
<Text key={key} selectable={Platform.OS === "web"} style={[styles.text, textStyle]}>
|
|
121
122
|
{renderInlines(block.tokens ?? Lexer.lexInline(block.text ?? block.raw), styles, key)}
|
|
122
123
|
</Text>
|
|
123
124
|
);
|
|
@@ -126,24 +127,79 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
126
127
|
});
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/** The speaker run into the start of a line: the seal's blue, then the words. */
|
|
131
|
+
function Lead({ name, styles }: { name: string; styles: Styles }) {
|
|
132
|
+
return <Text style={styles.lead}>{name}{" "}</Text>;
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
/** One line (or a few) of Markdown, inline only: the collapsed preview on a notification card. */
|
|
130
|
-
export function SquareInline({ text, theme, numberOfLines,
|
|
136
|
+
export function SquareInline({ text, theme, numberOfLines, lead }: {
|
|
131
137
|
text: string;
|
|
132
138
|
theme: PluginTheme;
|
|
133
139
|
numberOfLines?: number;
|
|
134
|
-
|
|
140
|
+
lead?: string;
|
|
135
141
|
}) {
|
|
136
142
|
const styles = useMemo(() => makeStyles(theme.colors), [theme.colors]);
|
|
137
143
|
return (
|
|
138
|
-
<Text numberOfLines={numberOfLines} style={
|
|
144
|
+
<Text numberOfLines={numberOfLines} style={styles.text}>
|
|
145
|
+
{lead !== undefined && <Lead name={lead} styles={styles} />}
|
|
139
146
|
{renderInlines(Lexer.lexInline(text), styles, "inline")}
|
|
140
147
|
</Text>
|
|
141
148
|
);
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
/** A
|
|
145
|
-
|
|
151
|
+
/** A paragraph that is one *emphasis* and nothing else is an action, not a line. */
|
|
152
|
+
const isAction = (token: Token): token is Tokens.Paragraph =>
|
|
153
|
+
token.type === "paragraph" && children(token).length === 1 && children(token)[0]!.type === "em";
|
|
154
|
+
|
|
155
|
+
/** A bell or an action is stage direction: a caption on a hairline across the full width. */
|
|
156
|
+
export function SquareCue({ label, theme }: { label: ReactNode; theme: PluginTheme }) {
|
|
157
|
+
const rule = { flex: 1, height: StyleSheet.hairlineWidth, backgroundColor: theme.colors.border };
|
|
158
|
+
return (
|
|
159
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, marginVertical: 2 }}>
|
|
160
|
+
<View style={rule} />
|
|
161
|
+
<Text style={{ color: theme.colors.foregroundMuted, fontSize: 11.5, lineHeight: 16, letterSpacing: 0.4 }}>{label}</Text>
|
|
162
|
+
<View style={rule} />
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A body of Markdown: no envelope, no route line — the words only, at full width.
|
|
169
|
+
* `lead` runs the speaker into the first line; `actor` names whoever performs an action.
|
|
170
|
+
*/
|
|
171
|
+
export function SquareMarkdownBody({ text, theme, lead, actor }: {
|
|
172
|
+
text: string;
|
|
173
|
+
theme: PluginTheme;
|
|
174
|
+
lead?: string;
|
|
175
|
+
actor?: string;
|
|
176
|
+
}) {
|
|
146
177
|
const styles = useMemo(() => makeStyles(theme.colors), [theme.colors]);
|
|
147
|
-
const tokens = useMemo(() => lexer(text), [text]);
|
|
148
|
-
|
|
178
|
+
const tokens = useMemo(() => lexer(text).filter(token => token.type !== "space"), [text]);
|
|
179
|
+
let pending = lead;
|
|
180
|
+
const nodes = tokens.map((token, index) => {
|
|
181
|
+
const key = `body-${index}`;
|
|
182
|
+
if (isAction(token)) {
|
|
183
|
+
const words = renderInlines(children(children(token)[0]!), styles, key);
|
|
184
|
+
return <SquareCue key={key} theme={theme} label={actor === undefined ? words : <>{actor} {words}</>} />;
|
|
185
|
+
}
|
|
186
|
+
if (pending === undefined) return renderBlocks([token], styles, key);
|
|
187
|
+
const name = pending;
|
|
188
|
+
pending = undefined;
|
|
189
|
+
if (token.type === "paragraph") {
|
|
190
|
+
return (
|
|
191
|
+
<Text key={key} selectable={Platform.OS === "web"} style={styles.text}>
|
|
192
|
+
<Lead name={name} styles={styles} />
|
|
193
|
+
{renderInlines(children(token), styles, key)}
|
|
194
|
+
</Text>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return [<Text key={`${key}-lead`} style={styles.text}><Lead name={name} styles={styles} /></Text>, ...renderBlocks([token], styles, key)];
|
|
198
|
+
});
|
|
199
|
+
return (
|
|
200
|
+
<View style={styles.block}>
|
|
201
|
+
{nodes}
|
|
202
|
+
{pending !== undefined && <Text style={styles.text}><Lead name={pending} styles={styles} /></Text>}
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
149
205
|
}
|