Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
258 commits
Select commit Hold shift + click to select a range
f4dd07f
test(reference-host): define collaboration lifecycle ownership contract
seonghobae Aug 20, 2026
8f8a55e
feat(reference-host): demonstrate host collaboration lifecycle
seonghobae Aug 20, 2026
25c847d
test(reference-host): derive recovery UX from Inkspan transitions
seonghobae Aug 20, 2026
d68bee4
fix(reference-host): derive autosave recovery from snapshots
seonghobae Aug 20, 2026
508576c
docs(reference-host): align guide with executable fixtures
seonghobae Aug 20, 2026
675c897
test(reference-host): reject persistence accessors
seonghobae Aug 20, 2026
733937d
fix(reference-host): contain persistence accessors
seonghobae Aug 20, 2026
0e0503b
test(reference-host): reject proposal accessors
seonghobae Aug 20, 2026
d98401e
fix(reference-host): contain proposal accessors
seonghobae Aug 20, 2026
c0c2d1a
test(reference-host): reject autosave accessors
seonghobae Aug 20, 2026
da17ddb
fix(reference-host): contain autosave accessors
seonghobae Aug 20, 2026
b590800
test(reference-host): reject collaboration accessors
seonghobae Aug 20, 2026
c2a10b1
fix(reference-host): contain collaboration accessors
seonghobae Aug 20, 2026
3314838
test(reference-host): contain teardown failures
seonghobae Aug 20, 2026
43864c6
fix(reference-host): finish cleanup after teardown failure
seonghobae Aug 20, 2026
da4f2b4
test(reference-host): require real host-created Y.Doc lifecycle
seonghobae Aug 20, 2026
d34c526
feat(reference-host): exercise host-created Y.Doc lifecycle
seonghobae Aug 20, 2026
a0eb8af
docs(reference-host): record real Y.Doc lifecycle evidence
seonghobae Aug 20, 2026
a11a759
test(reference-host): require initialization unwind
seonghobae Aug 20, 2026
091e73d
fix(reference-host): unwind failed collaboration initialization
seonghobae Aug 20, 2026
a135bbf
test(reference-host): require public presentation asset wiring
seonghobae Aug 20, 2026
32e9e69
feat(reference-host): wire full multilingual presentation assets
seonghobae Aug 20, 2026
d021fcd
feat(reference-host): wire Latin presentation assets
seonghobae Aug 20, 2026
b2a7eba
docs(reference-host): document public presentation options
seonghobae Aug 20, 2026
f198c87
test(reference-host): require retry restore and fork recovery
seonghobae Aug 20, 2026
35dbe4f
feat(reference-host): prove retry restore and fork recovery
seonghobae Aug 20, 2026
fc7479d
test(reference-host): require recovery guide parity
seonghobae Aug 20, 2026
e604320
docs(reference-host): document recovery semantics
seonghobae Aug 20, 2026
1c5ae7c
test(reference-host): contain hostile reflection failures
seonghobae Aug 20, 2026
f9158f8
test(reference-host): exercise real reflection boundaries
seonghobae Aug 20, 2026
da5033b
fix(reference-host): contain hostile reflection errors
seonghobae Aug 20, 2026
d163652
fix(reference-host): redact proposal reflection failures
seonghobae Aug 20, 2026
dc3fcbd
fix(reference-host): contain autosave reflection failures
seonghobae Aug 20, 2026
2b43b00
test(reference-host): cover hostile resource reflection
seonghobae Aug 20, 2026
7c837e7
test(reference-host): cover hostile provider failures
seonghobae Aug 20, 2026
c6fd4f7
fix(reference-host): contain collaboration reflection failures
seonghobae Aug 20, 2026
39da20a
test(reference-host): retain failed provider for retry
seonghobae Aug 20, 2026
916fc94
fix(reference-host): retry failed provider destruction
seonghobae Aug 20, 2026
f9cd1dc
test(reference-host): require empty document persistence
seonghobae Aug 20, 2026
5213b43
fix(reference-host): allow empty document bodies
seonghobae Aug 20, 2026
db11957
test(reference-host): allow empty proposal replacement
seonghobae Aug 20, 2026
7ec8541
fix(reference-host): permit empty proposal replacement
seonghobae Aug 20, 2026
8e49936
test(reference-host): reject empty autosave validators
seonghobae Aug 20, 2026
0cc48b6
fix(reference-host): reject empty autosave validators
seonghobae Aug 20, 2026
b62af9b
test(reference-host): require observed retry before recovery
seonghobae Aug 21, 2026
a7f2c5a
fix(reference-host): require retry before recovery state
seonghobae Aug 21, 2026
20e0be5
test(reference-host): redact reconnect provider failures
seonghobae Aug 21, 2026
783dc9e
fix(reference-host): redact reconnect provider failures
seonghobae Aug 21, 2026
577512a
test(reference-host): redact host collaboration callback failures
seonghobae Aug 21, 2026
64212ae
fix(reference-host): redact collaboration callback failures
seonghobae Aug 21, 2026
c9b4a21
test(reference-host): bound prototype traversal
seonghobae Aug 21, 2026
be208da
fix(reference-host): bound prototype discovery
seonghobae Aug 21, 2026
ac130d0
test(reference-host): redact proposal apply failures
seonghobae Aug 21, 2026
1e45d14
fix(reference-host): redact proposal apply failures
seonghobae Aug 21, 2026
8f1cebd
test(reference-host): model ambiguous committed writes
seonghobae Aug 21, 2026
1d7b07b
test(reference-host): distinguish ambiguous commit
seonghobae Aug 21, 2026
b7715e1
fix(reference-host): model ambiguous committed writes
seonghobae Aug 21, 2026
5143fe5
test(reference-host): bind ambiguity guidance
seonghobae Aug 21, 2026
4b211fe
docs(reference-host): clarify ambiguous recovery
seonghobae Aug 21, 2026
cc64cc1
test(reference-host): define native form journey contract
seonghobae Aug 21, 2026
cfae154
feat(reference-host): add native form buyer journey
seonghobae Aug 21, 2026
19b95ed
docs(reference-host): record native form buyer journey
seonghobae Aug 21, 2026
f81dfff
test(reference-host): resolve fixture path from repository root
seonghobae Aug 21, 2026
4a5d593
test(reference-host): require single-flight authorized submit
seonghobae Aug 21, 2026
a4ddda4
fix(reference-host): serialize authorized submissions
seonghobae Aug 21, 2026
ae5ce15
fix(reference-host): guard overlapping native submits
seonghobae Aug 21, 2026
7c09378
test(reference-host): bind native form to single-flight submit
seonghobae Aug 21, 2026
de3f2a4
test(reference-host): require hydration-gated editor mount
seonghobae Aug 21, 2026
c53f9c6
feat(reference-host): add hydration-gated editor mount
seonghobae Aug 21, 2026
3e99bbb
test(reference-host): require packed artifact acceptance
seonghobae Aug 21, 2026
1b87cc0
feat(reference-host): verify exact packed artifact consumer
seonghobae Aug 21, 2026
297d2c3
test(reference-host): correct packed consumer install contract
seonghobae Aug 21, 2026
1c92709
fix(reference-host): validate consumer dependency resolution truthfully
seonghobae Aug 21, 2026
afe0236
test(reference-host): contain submission observer failures
seonghobae Aug 21, 2026
bfa9158
fix(reference-host): isolate submission observers
seonghobae Aug 21, 2026
95d60b4
test(reference-host): keep reset status honest
seonghobae Aug 21, 2026
8e6c941
fix(reference-host): keep reset save state consistent
seonghobae Aug 21, 2026
721a6c0
test(reference-host): keep native form contract source-isolated
seonghobae Aug 21, 2026
f0401c6
fix(reference-host): keep submission callback non-null
seonghobae Aug 21, 2026
3e65eda
test(reference-host): require read-only form fail-closed contract
seonghobae Aug 21, 2026
cf0c060
feat(reference-host): fail-close native form when read-only
seonghobae Aug 21, 2026
fca5b90
test(reference-host): align form assertions with read-only guard
seonghobae Aug 21, 2026
529b549
test(reference-host): require packed asset containment evidence
seonghobae Aug 21, 2026
2bc48a2
fix(reference-host): verify packed public asset containment
seonghobae Aug 21, 2026
9654d26
test(reference-host): require explicit client hydration boundary
seonghobae Aug 21, 2026
3eb10fb
fix(reference-host): mark hydration gate as client boundary
seonghobae Aug 21, 2026
9bc7ef9
test(reference-host): verify packed ESM and CommonJS consumers
seonghobae Aug 21, 2026
fc0add2
fix(reference-host): preserve packed verifier receipt contract
seonghobae Aug 21, 2026
2801626
test(reference-host): expose TypeScript import-guard gap
seonghobae Aug 21, 2026
6db306d
test(reference-host): guard all executable example imports
seonghobae Aug 21, 2026
c85a746
test(reference-host): expose synchronous submission gate
seonghobae Aug 21, 2026
77cd38d
test(reference-host): require synchronous form admission gate
seonghobae Aug 21, 2026
f8afbb7
fix(reference-host): expose synchronous submission admission
seonghobae Aug 21, 2026
f7bb0ea
fix(reference-host): bind form mutations to synchronous gate
seonghobae Aug 21, 2026
25fda4a
test(reference-host): reject unknown proposal fields
seonghobae Aug 21, 2026
199b80f
fix(reference-host): fail closed on unknown proposal fields
seonghobae Aug 21, 2026
47289f3
test(reference-host): reject unknown persistence fields
seonghobae Aug 21, 2026
2d1a591
fix(reference-host): fail closed on unknown persistence fields
seonghobae Aug 21, 2026
25ec179
test(reference-host): reject unknown collaboration option fields
seonghobae Aug 21, 2026
5c8aca0
fix(reference-host): reject unknown collaboration option fields
seonghobae Aug 21, 2026
df64f92
test(reference-host): reject unknown autosave snapshot fields
seonghobae Aug 21, 2026
ba723f6
fix(reference-host): require exact autosave snapshot shape
seonghobae Aug 21, 2026
88feb70
test(reference-host): expose incomplete collaboration teardown
seonghobae Aug 21, 2026
2b56d0d
fix(reference-host): retry incomplete collaboration teardown
seonghobae Aug 21, 2026
27d2e21
test(reference-host): lock partial teardown to cleanup
seonghobae Aug 21, 2026
a03f8f9
fix(reference-host): lock partial collaboration teardown
seonghobae Aug 21, 2026
aa174c0
test(reference-host): reject fork validator aliasing
seonghobae Aug 22, 2026
7f564f5
fix(reference-host): namespace fork validators
seonghobae Aug 22, 2026
dc6ea30
test(reference-host): lock fork validator namespace
seonghobae Aug 22, 2026
d235b8c
test(reference-host): bound nested fork validators
seonghobae Aug 22, 2026
1e3dabb
fix(reference-host): bound fork validator namespaces
seonghobae Aug 22, 2026
5c96976
test(reference-host): require packed markdown handoff
seonghobae Aug 22, 2026
48e27fe
feat(reference-host): verify packed markdown handoff
seonghobae Aug 22, 2026
f970ae1
test(reference-host): lock ambiguous connect quarantine
seonghobae Aug 22, 2026
f23f1a8
fix(reference-host): quarantine ambiguous provider connections
seonghobae Aug 22, 2026
ec7f78d
test(reference-host): align connect failure recovery contract
seonghobae Aug 22, 2026
271ade4
docs(reference-host): reconcile executable slice inventory
seonghobae Aug 22, 2026
1b5d346
test(reference-host): keep buyer inventory code-current
seonghobae Aug 22, 2026
c625004
test(reference-host): require packed autosave observer wiring
seonghobae Aug 22, 2026
94e64dc
feat(reference-host): wire packed autosave observer
seonghobae Aug 22, 2026
8b14d1e
test(reference-host): require autosave wiring inventory truth
seonghobae Aug 22, 2026
f12e0da
docs(reference-host): record packed autosave observer wiring
seonghobae Aug 22, 2026
96b871e
test(reference-host): require bounded Office handoff
seonghobae Aug 22, 2026
226cf92
feat(reference-host): add bounded Office handoff
seonghobae Aug 22, 2026
f14ebb4
docs(reference-host): document Office handoff boundary
seonghobae Aug 22, 2026
dc5e9d8
docs(reference-host): preserve browser acceptance contract
seonghobae Aug 22, 2026
c855622
test(reference-host): require application-level hydration
seonghobae Aug 22, 2026
fde3b0c
feat(reference-host): compose application hydration boundary
seonghobae Aug 22, 2026
45b2117
test(reference-host): keep hydration proof at package boundary
seonghobae Aug 22, 2026
029b6eb
docs(reference-host): inventory application composition
seonghobae Aug 22, 2026
7d03815
test(reference-host): require real browser hydration journey
seonghobae Aug 22, 2026
fe9cbba
feat(reference-host): add deterministic browser shell
seonghobae Aug 22, 2026
ee9bc97
feat(reference-host): hydrate browser host application
seonghobae Aug 22, 2026
b32fbe8
docs(reference-host): inventory browser host evidence
seonghobae Aug 22, 2026
a700a21
fix(reference-host): preserve hydration shell text nodes
seonghobae Aug 22, 2026
9a0cc37
test(reference-host): cover read-only browser journey
seonghobae Aug 22, 2026
bc0b357
docs(reference-host): record read-only browser evidence
seonghobae Aug 22, 2026
4198b4c
test(reference-host): cover narrow viewport journey
seonghobae Aug 22, 2026
a7c120b
docs(reference-host): record narrow viewport evidence
seonghobae Aug 22, 2026
a05dd94
test(reference-host): exercise buyer print journey
seonghobae Aug 22, 2026
b851c18
fix(reference-host): load public editor presentation contract
seonghobae Aug 22, 2026
95dcc93
docs(reference-host): align buyer print evidence
seonghobae Aug 22, 2026
a589f65
test(reference-host): exercise forced-colors buyer journey
seonghobae Aug 22, 2026
6649758
test(reference-host): require packed browser package binding
seonghobae Aug 22, 2026
5ceae9a
fix(reference-host): bind release browser to packed package
seonghobae Aug 22, 2026
93a06e1
test(reference-host): bound autosave validators
seonghobae Aug 22, 2026
7ea44dc
fix(reference-host): bound autosave validators
seonghobae Aug 22, 2026
afd6f57
test(reference-host): reject malformed strong entity tags
seonghobae Aug 22, 2026
890be94
fix(reference-host): validate strong entity-tag syntax
seonghobae Aug 22, 2026
08eb81d
test(reference-host): require controlled and uncontrolled form modes
seonghobae Aug 22, 2026
93512cd
feat(reference-host): demonstrate controlled editor composition
seonghobae Aug 22, 2026
1b43592
test(reference-host): match conditional controlled binding
seonghobae Aug 22, 2026
89cc471
test(ci): require packed reference-host browser artifact
seonghobae Aug 22, 2026
8aa2370
fix(ci): bind reference-host browser evidence to packed artifact
seonghobae Aug 22, 2026
49eae9a
fix(ci): align packed browser identity contract
seonghobae Aug 22, 2026
369b963
test(reference-host): contain Office handoff reflection
seonghobae Aug 22, 2026
1bc2075
test(reference-host): isolate Office reflection harness
seonghobae Aug 22, 2026
f82d09a
fix(reference-host): contain Office handoff reflection
seonghobae Aug 22, 2026
04f307f
test(reference-host): expose async proposal apply false-success
seonghobae Aug 22, 2026
b94845b
fix(reference-host): settle async proposal callbacks
seonghobae Aug 22, 2026
4472094
test(reference-host): expose dropped control mode
seonghobae Aug 22, 2026
f530b71
fix(reference-host): preserve app control mode
seonghobae Aug 22, 2026
d07ca94
test(reference-host): reject async provider connect false-success
seonghobae Aug 22, 2026
3976752
fix(reference-host): quarantine async provider connect
seonghobae Aug 22, 2026
8228971
test(reference-host): reject async provider teardown false-success
seonghobae Aug 22, 2026
31970f3
test(reference-host): cover async document teardown false-success
seonghobae Aug 22, 2026
931f6f9
test(reference-host): cover async provider disconnect false-success
seonghobae Aug 22, 2026
93dcc0b
fix(reference-host): contain async provider teardown
seonghobae Aug 22, 2026
05ab985
test(reference-host): reproduce stale saved state after edits
seonghobae Aug 23, 2026
39fcd98
test(reference-host): exercise saved-state invalidation in browser
seonghobae Aug 23, 2026
f53a68b
test(reference-host): move dirty-state RED to packed browser
seonghobae Aug 23, 2026
f2156ce
fix(ci): run every declared browser acceptance spec
seonghobae Aug 23, 2026
c0e97bb
test(reference-host): allow deterministic deferred save journey
seonghobae Aug 23, 2026
03beed6
test(reference-host): reproduce edit during in-flight save
seonghobae Aug 23, 2026
3b89870
test(reference-host): type browser network guard explicitly
seonghobae Aug 23, 2026
5580c35
fix(reference-host): invalidate stale saved presentation
seonghobae Aug 23, 2026
65cb6cf
test(reference-host): contract truthful dirty-state projection
seonghobae Aug 23, 2026
85532ca
fix(reference-host): invalidate stale save state on native input
seonghobae Aug 23, 2026
c2f137b
test(reference-host): contract native dirty-state fallback
seonghobae Aug 23, 2026
db68844
test(reference-host): make save-state assertions exact
seonghobae Aug 23, 2026
61cdec2
test(reference-host): require browser presentation entrypoint
seonghobae Aug 23, 2026
74e2160
test(browser): bind packed font entrypoints
seonghobae Aug 23, 2026
2a16930
feat(reference-host): exercise full packed presentation
seonghobae Aug 23, 2026
6a145db
docs(reference-host): align packed browser evidence
seonghobae Aug 23, 2026
e0a6c38
test(reference-host): align packed presentation binding
seonghobae Aug 23, 2026
eca9a3a
test(reference-host): align partial-app acceptance contract
seonghobae Aug 23, 2026
f2f3683
test(reference-host): verify pre-hydration shell transition
seonghobae Aug 23, 2026
1776469
test(reference-host): exercise packed native form reset
seonghobae Aug 23, 2026
33aafef
test(browser): pin optimizer entry contract
seonghobae Aug 23, 2026
942250e
fix(browser): stabilize shared Vite dependency graph
seonghobae Aug 23, 2026
ba6da1a
test(reference-host): require executable Office handoff acceptance
seonghobae Aug 23, 2026
3084ee9
feat(reference-host): execute packed Office handoff
seonghobae Aug 23, 2026
4b69e58
ci(reference-host): verify exact packed Office execution
seonghobae Aug 23, 2026
2722f86
test(reference-host): pin Office handoff export name
seonghobae Aug 23, 2026
bcc51c0
fix(ci): quote Office pip cache command
seonghobae Aug 23, 2026
f13b726
fix(reference-host): invoke canonical Office handoff export
seonghobae Aug 23, 2026
58699fc
test(reference-host): keep Office execution docs truthful
seonghobae Aug 23, 2026
cbfe14a
docs(reference-host): align Office execution evidence
seonghobae Aug 23, 2026
a34dc33
test(reference-host): require controlled browser composition
seonghobae Aug 23, 2026
dda8bc5
fix(reference-host): route controlled browser mode
seonghobae Aug 23, 2026
02475eb
fix(reference-host): expose active form control mode
seonghobae Aug 23, 2026
9ac26a0
test(reference-host): preserve form contract with mode marker
seonghobae Aug 23, 2026
9f6bc03
test(reference-host): enter heading content without markdown marker
seonghobae Aug 23, 2026
2a950c5
test(reference-host): assert rendered heading text on reset
seonghobae Aug 23, 2026
898bc4a
test(reference-host): prove read-only form boundary
seonghobae Aug 23, 2026
e86fd96
test(reference-host): require host authorization before collaboration…
seonghobae Aug 23, 2026
5aa0d7e
feat(reference-host): gate collaboration provider with host authoriza…
seonghobae Aug 23, 2026
f44b327
docs(reference-host): synchronize authorized collaboration inventory
seonghobae Aug 23, 2026
1b6b505
test(reference-host): require one-command buyer journey
seonghobae Aug 23, 2026
9ea1bd1
feat(reference-host): add current journey verifier
seonghobae Aug 23, 2026
0f89e06
docs(reference-host): expose current journey command
seonghobae Aug 23, 2026
71c3f84
test(reference-host): enforce runtime env independence
seonghobae Aug 23, 2026
d8884dc
test(reference-host): scope env contract to runtime files
seonghobae Aug 23, 2026
3423d82
test(reference-host): cover same-turn reset admission
seonghobae Aug 23, 2026
c33c420
test(reference-host): require browser journey in one-command plan
seonghobae Aug 23, 2026
b6c05b2
feat(reference-host): verify packed browser journey
seonghobae Aug 23, 2026
dbbb2a9
feat(reference-host): include browser acceptance in journey
seonghobae Aug 23, 2026
308669b
docs(reference-host): document scoped browser verifier
seonghobae Aug 23, 2026
52ecab7
test(reference-host): verify packed browser plan contract
seonghobae Aug 23, 2026
ff72919
test(reference-host): require self-contained Office journey
seonghobae Aug 24, 2026
7ca2f8f
feat(reference-host): self-contain packed Office journey
seonghobae Aug 24, 2026
8f34d64
fix(reference-host): execute self-contained packed Office step
seonghobae Aug 24, 2026
10438f6
docs(reference-host): distinguish packed Office orchestration
seonghobae Aug 24, 2026
559800e
test(reference-host): bind Office docs to packed authority split
seonghobae Aug 24, 2026
d6bbf03
ci(reference-host): execute self-contained packed Office journey
seonghobae Aug 24, 2026
4249c5e
test(reference-host): require narrow client hydration boundary
seonghobae Aug 24, 2026
a8012c5
feat(reference-host): isolate client hydration boundary
seonghobae Aug 24, 2026
743ce42
fix(reference-host): keep server shell outside client boundary
seonghobae Aug 24, 2026
64c3714
docs(reference-host): describe server and client boundary
seonghobae Aug 24, 2026
f2368c6
chore(reference-host): restore canonical CI ownership
seonghobae Aug 24, 2026
325d07c
test(reference-host): keep Office contract source-owned
seonghobae Aug 24, 2026
e86c27a
test(reference-host): drop foreign CI assertion
seonghobae Aug 24, 2026
031ac14
test(reference-host): require exact-packed app SSR proof
seonghobae Aug 24, 2026
20fdb0b
feat(reference-host): verify exact-packed app SSR
seonghobae Aug 24, 2026
23529fe
fix(reference-host): resolve SSR through packed consumer
seonghobae Aug 24, 2026
cfc18fe
docs(reference-host): bind exact-packed app SSR evidence
seonghobae Aug 24, 2026
a41000b
test(reference-host): require app SSR in one-command journey
seonghobae Aug 24, 2026
e02b7bc
fix(reference-host): include app SSR in current journey
seonghobae Aug 24, 2026
2805bdc
test(reference-host): require authorized collaboration in buyer journey
seonghobae Aug 25, 2026
ffba554
feat(reference-host): verify host collaboration authorization
seonghobae Aug 25, 2026
a76849b
fix(reference-host): run authorized collaboration journey
seonghobae Aug 25, 2026
3426b1f
test(reference-host): preserve Office paragraph boundaries
seonghobae Aug 25, 2026
33ff62d
fix(reference-host): preserve Office paragraph boundaries
seonghobae Aug 25, 2026
99a02f1
test(reference-host): verify multi-paragraph Office handoff
seonghobae Aug 25, 2026
46e0349
test(reference-host): prove packed framework-free buyer boundary
seonghobae Aug 25, 2026
94d2e81
test(reference-host): bound packed package setup time
seonghobae Aug 25, 2026
9536306
test(reference-host): reproduce same-turn reset admission gap
seonghobae Aug 25, 2026
da627ef
fix(reference-host): expose synchronous form mutation admission
seonghobae Aug 25, 2026
37cfc42
fix(reference-host): bind form handlers to synchronous admission
seonghobae Aug 25, 2026
1fb2d31
test(reference-host): assert shared synchronous form admission
seonghobae Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions examples/reference-host/README.md

Large diffs are not rendered by default.

310 changes: 310 additions & 0 deletions examples/reference-host/autosave-view-model.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
const AUTOSAVE_STATES = new Set([
'idle',
'saving',
'blocked',
'closing',
'closed',
]);
const BLOCKED_REASONS = new Set(['conflict', 'failure']);
const MAX_STRONG_ENTITY_TAG_CODE_UNITS = 256;
const STRONG_ENTITY_TAG_PATTERN = /^"[\x21\x23-\x7e\x80-\xff]*"$/u;
const SNAPSHOT_KEYS = [
'state',
'blockedReason',
'activeStrongEntityTag',
'pendingStrongEntityTag',
'lastSavedStrongEntityTag',
];

/** Marker used by repository contracts to prevent this host fixture becoming runtime authority. */
export const REFERENCE_ONLY = true;

function requireNullableStrongEntityTag(value, label) {
if (
value !== null &&
(typeof value !== 'string' ||
value.length > MAX_STRONG_ENTITY_TAG_CODE_UNITS ||
!STRONG_ENTITY_TAG_PATTERN.test(value))
) {
throw new TypeError(`${label} is invalid.`);
}
return value;
}

function snapshotData(source) {
try {
if (typeof source !== 'object' || source === null) {
throw new TypeError('autosave snapshot is invalid.');
}
const values = Object.create(null);
for (const key of SNAPSHOT_KEYS) {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (
descriptor === undefined ||
!Object.prototype.hasOwnProperty.call(descriptor, 'value')
) {
throw new TypeError('autosave snapshot is invalid.');
}
values[key] = descriptor.value;
}
const ownKeys = Reflect.ownKeys(source);
if (
ownKeys.length !== SNAPSHOT_KEYS.length ||
ownKeys.some(
(key) => typeof key !== 'string' || !SNAPSHOT_KEYS.includes(key),
)
) {
throw new TypeError('autosave snapshot is invalid.');
}
return values;
} catch {
throw new TypeError('autosave snapshot is invalid.');
}
}

function readSnapshot(source) {
const snapshot = snapshotData(source);
if (
!AUTOSAVE_STATES.has(snapshot.state) ||
(snapshot.blockedReason !== null &&
!BLOCKED_REASONS.has(snapshot.blockedReason))
) {
throw new TypeError('autosave snapshot is invalid.');
}

if (
(snapshot.state === 'blocked' && snapshot.blockedReason === null) ||
(snapshot.state !== 'blocked' && snapshot.blockedReason !== null)
) {
throw new TypeError('autosave snapshot lifecycle is inconsistent.');
}

return Object.freeze({
state: snapshot.state,
blockedReason: snapshot.blockedReason,
activeStrongEntityTag: requireNullableStrongEntityTag(
snapshot.activeStrongEntityTag,
'activeStrongEntityTag',
),
pendingStrongEntityTag: requireNullableStrongEntityTag(
snapshot.pendingStrongEntityTag,
'pendingStrongEntityTag',
),
lastSavedStrongEntityTag: requireNullableStrongEntityTag(
snapshot.lastSavedStrongEntityTag,
'lastSavedStrongEntityTag',
),
});
}

function presentation(viewState) {
return Object.freeze({
viewState,
messageKey: `referenceHost.autosave.${viewState}`,
busy:
viewState === 'saving' ||
viewState === 'queued' ||
viewState === 'retrying',
blocked: viewState === 'conflict' || viewState === 'failed',
canRetry: viewState === 'conflict' || viewState === 'failed',
});
}

/**
* Create one host-owned projection of Inkspan autosave lifecycle transitions.
*
* `observe()` consumes only programmatic queue/session snapshots. Snapshot fields
* must be an exact own-data-property shape so presentation never invokes
* caller-owned accessors or silently admits authority-looking metadata. Strong
* entity tags are validated as bounded RFC 9110 strong entity-tags before
* projection so untrusted snapshot metadata cannot masquerade as a durable
* validator or allocate unbounded retained strings at this reference boundary.
* A blocked to saving transition is presented as retrying, and only a later idle
* transition after that observed retry is presented as recovered. A blocked to
* idle transition without an intervening save returns to clean instead of
* manufacturing recovery evidence. The projection never returns local or durable
* validators; hosts localize `messageKey` and keep authenticated recovery controls
* outside Inkspan.
*/
export function createAutosaveViewModel() {
let retryPending = false;
let retryInFlight = false;

function clearRetryEvidence() {
retryPending = false;
retryInFlight = false;
}

function observe(snapshot) {
const current = readSnapshot(snapshot);

if (current.state === 'blocked') {
retryPending = true;
retryInFlight = false;
return presentation(
current.blockedReason === 'conflict' ? 'conflict' : 'failed',
);
}
if (current.state === 'closing') {
clearRetryEvidence();
return presentation('closing');
}
if (current.state === 'closed') {
clearRetryEvidence();
return presentation('closed');
}
if (current.state === 'saving' && (retryPending || retryInFlight)) {
retryPending = false;
retryInFlight = true;
return presentation('retrying');
}
if (current.state === 'saving' && current.pendingStrongEntityTag !== null) {
return presentation('queued');
}
if (current.state === 'saving') return presentation('saving');
if (retryInFlight) {
clearRetryEvidence();
return presentation('recovered');
}
retryPending = false;
return presentation('clean');
}

return Object.freeze({ observe });
}

function snapshot({
state,
blockedReason = null,
activeStrongEntityTag = null,
pendingStrongEntityTag = null,
lastSavedStrongEntityTag = null,
}) {
return Object.freeze({
state,
blockedReason,
activeStrongEntityTag,
pendingStrongEntityTag,
lastSavedStrongEntityTag,
});
}

function runSelfTest() {
const steady = createAutosaveViewModel();
const clean = steady.observe(snapshot({ state: 'idle' })).viewState;
const saving = steady.observe(
snapshot({
state: 'saving',
activeStrongEntityTag: '"local-active"',
}),
).viewState;
const queued = steady.observe(
snapshot({
state: 'saving',
activeStrongEntityTag: '"local-active"',
pendingStrongEntityTag: '"local-pending"',
}),
).viewState;

const recovery = createAutosaveViewModel();
const conflict = recovery.observe(
snapshot({ state: 'blocked', blockedReason: 'conflict' }),
).viewState;
const retrying = recovery.observe(
snapshot({
state: 'saving',
activeStrongEntityTag: '"local-retry"',
}),
).viewState;
const recovered = recovery.observe(
snapshot({
state: 'idle',
lastSavedStrongEntityTag: '"local-saved"',
}),
).viewState;

const failed = createAutosaveViewModel().observe(
snapshot({ state: 'blocked', blockedReason: 'failure' }),
).viewState;
const closing = createAutosaveViewModel().observe(
snapshot({ state: 'closing' }),
).viewState;
const closed = createAutosaveViewModel().observe(
snapshot({ state: 'closed' }),
).viewState;

process.stdout.write(
`${JSON.stringify({
clean,
closed,
closing,
conflict,
failed,
queued,
recovered,
retrying,
saving,
})}\n`,
);
}

function runInvalidValidatorSelfTest() {
function observeError(candidate) {
try {
createAutosaveViewModel().observe(candidate);
return null;
} catch (error) {
return error instanceof Error ? error.message : 'unexpected error';
}
}

const activeError = observeError(
snapshot({ state: 'saving', activeStrongEntityTag: '' }),
);
const pendingError = observeError(
snapshot({
state: 'saving',
activeStrongEntityTag: '"local-active"',
pendingStrongEntityTag: '',
}),
);
const lastSavedError = observeError(
snapshot({ state: 'idle', lastSavedStrongEntityTag: '' }),
);

process.stdout.write(
`${JSON.stringify({ activeError, lastSavedError, pendingError })}\n`,
);
}

function runHostileAccessorSelfTest() {
let getterCalls = 0;
let error = null;
const hostile = {
blockedReason: null,
activeStrongEntityTag: null,
pendingStrongEntityTag: null,
lastSavedStrongEntityTag: null,
};
Object.defineProperty(hostile, 'state', {
enumerable: true,
get() {
getterCalls += 1;
return 'idle';
},
});
try {
createAutosaveViewModel().observe(hostile);
} catch (failure) {
error = failure instanceof Error ? failure.message : 'unexpected error';
}
process.stdout.write(`${JSON.stringify({ error, getterCalls })}\n`);
}

if (process.argv.includes('--invalid-validator-self-test')) {
runInvalidValidatorSelfTest();
} else if (process.argv.includes('--hostile-accessor-self-test')) {
runHostileAccessorSelfTest();
} else if (process.argv.includes('--self-test')) {
runSelfTest();
}
12 changes: 12 additions & 0 deletions examples/reference-host/browser-host.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Inkspan reference host</title>
</head>
<body>
<div id="reference-host-root"><main aria-labelledby="reference-host-heading"><h1 id="reference-host-heading">Inkspan reference host</h1><div aria-busy="true">Loading buyer editor</div></main></div>
<script type="module" src="./browser-host.tsx"></script>
</body>
</html>
50 changes: 50 additions & 0 deletions examples/reference-host/browser-host.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import './presentation-full.css';
import { hydrateRoot } from 'react-dom/client';

import { ReferenceHostApp } from './reference-host-app.js';

declare global {
interface Window {
referenceHostSubmissions: string[];
referenceHostResolveSubmission?: () => void;
}
}

const submissions: string[] = [];
window.referenceHostSubmissions = submissions;

const root = document.getElementById('reference-host-root');
if (!root) {
throw new Error('Reference host root is missing.');
}

const searchParams = new URLSearchParams(window.location.search);
const readOnly = searchParams.get('readOnly') === '1';
const deferSubmission = searchParams.get('deferSubmission') === '1';
const controlMode =
searchParams.get('controlMode') === 'controlled'
? 'controlled'
: 'uncontrolled';
let resolveDeferredSubmission: (() => void) | undefined;

window.referenceHostResolveSubmission = () => {
resolveDeferredSubmission?.();
};

hydrateRoot(
root,
<ReferenceHostApp
controlMode={controlMode}
loadingLabel="Loading buyer editor"
onAuthorizedSubmit={async (messageBody) => {
submissions.push(messageBody);
if (deferSubmission) {
await new Promise<void>((resolve) => {
resolveDeferredSubmission = resolve;
});
resolveDeferredSubmission = undefined;
}
}}
readOnly={readOnly}
/>,
);
Loading
Loading