refactor(tui): extract session timeline state
This commit is contained in:
parent
daf8e539bf
commit
a653744d78
15 changed files with 1897 additions and 586 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Quark Timeline Architecture — and How It Can Be Faster
|
||||
|
||||
An architecture review of the `opencode-quark-timeline` experiment: what the layers are, why the design is sound, and the *mechanical* reason it can beat the Solid Store timeline — with the actual code.
|
||||
An architecture review of the `opencode-quark-timeline` experiment: what the layers are, why the design is sound, and the _mechanical_ reason it can beat the Solid Store timeline — with the actual code.
|
||||
|
||||
## The Stack in One Picture
|
||||
|
||||
|
|
@ -34,13 +34,13 @@ improvements.
|
|||
|
||||
## The Core Idea: Identity and Equivalence Are Inputs, Not Discoveries
|
||||
|
||||
This is the entire architectural bet. Solid Store must *discover* what changed; Quark is *told* what identity and sameness mean, once, at construction:
|
||||
This is the entire architectural bet. Solid Store must _discover_ what changed; Quark is _told_ what identity and sameness mean, once, at construction:
|
||||
|
||||
```typescript title="packages/tui/src/routes/session/rows.ts" caption="The complete reconciliation policy is two functions"
|
||||
const state = Keyed.make({ key: rowKey, equivalent: sameRow })
|
||||
```
|
||||
|
||||
`rowKey` answers *"which slot is this?"* — including the subtle case where a group's key is the ref that **created** it, so refs moving to `pending` can never change identity. Keys are precomputed once at row construction with length-prefixed segments, so reconciliation pays zero key-extraction work:
|
||||
`rowKey` answers _"which slot is this?"_ — including the subtle case where a group's key is the ref that **created** it, so refs moving to `pending` can never change identity. Keys are precomputed once at row construction with length-prefixed segments, so reconciliation pays zero key-extraction work:
|
||||
|
||||
```typescript title="packages/tui/src/routes/session/rows.ts" caption="Keys are built once, at construction — rowKey is a field read"
|
||||
export type SessionRow = { readonly id: string } & ( /* ...variants... */ )
|
||||
|
|
@ -58,7 +58,7 @@ function rowKey(row: SessionRow) {
|
|||
}
|
||||
```
|
||||
|
||||
`sameRow` answers a different question: *"can any consumer tell these two values apart?"* It compares only render-relevant fields. If it says yes-they're-the-same, **nothing downstream runs at all**.
|
||||
`sameRow` answers a different question: _"can any consumer tell these two values apart?"_ It compares only render-relevant fields. If it says yes-they're-the-same, **nothing downstream runs at all**.
|
||||
|
||||
## Two Channels Instead of One
|
||||
|
||||
|
|
@ -66,8 +66,8 @@ A Solid Store exposes one reactive graph; every consumer subscribes into the sam
|
|||
|
||||
```typescript title="packages/quark/src/keyed.ts" start=4
|
||||
export interface Keyed<A, Key> {
|
||||
readonly slots: Readable<readonly Readable<A>[]> // fires ONLY on insert/remove/reorder
|
||||
readonly values: Readable<readonly A[]> // fires on any current value change
|
||||
readonly slots: Readable<readonly Readable<A>[]> // fires ONLY on insert/remove/reorder
|
||||
readonly values: Readable<readonly A[]> // fires on any current value change
|
||||
has(key: Key): boolean
|
||||
get(key: Key): Readable<A> | undefined
|
||||
set(values: readonly A[]): void
|
||||
|
|
@ -98,13 +98,15 @@ group row. Trace that value update through both systems.
|
|||
### Before: Solid Store path
|
||||
|
||||
```typescript caption="Old hot path — every access and write crosses a proxy"
|
||||
setRows(produce((draft) => {
|
||||
// 1. draft is a proxy — every property read is a trap
|
||||
// 2. finding the group row walks proxied array elements
|
||||
// 3. the mutation writes through proxy machinery
|
||||
// 4. Solid records fine-grained dependencies per touched path
|
||||
append(draft, ref, part, queuedStart(draft))
|
||||
}))
|
||||
setRows(
|
||||
produce((draft) => {
|
||||
// 1. draft is a proxy — every property read is a trap
|
||||
// 2. finding the group row walks proxied array elements
|
||||
// 3. the mutation writes through proxy machinery
|
||||
// 4. Solid records fine-grained dependencies per touched path
|
||||
append(draft, ref, part, queuedStart(draft))
|
||||
}),
|
||||
)
|
||||
// ...and on reconnect / revert / rebuild:
|
||||
setRows(reconcile(reduce()))
|
||||
// reconcile must re-derive identity from item references,
|
||||
|
|
@ -123,10 +125,10 @@ export function make<A, Key>(options: {
|
|||
readonly equivalent?: (left: A, right: A) => boolean
|
||||
}): Keyed<A, Key> {
|
||||
const slots = State.make<readonly Writable<A>[]>([]) // structure channel: one signal holding the slot array
|
||||
const byKey = new Map<Key, Writable<A>>() // identity → slot address, maintained by set/insert/remove
|
||||
const equivalent = options.equivalent ?? Object.is // declared sameness (sameRow in the TUI)
|
||||
const byKey = new Map<Key, Writable<A>>() // identity → slot address, maintained by set/insert/remove
|
||||
const equivalent = options.equivalent ?? Object.is // declared sameness (sameRow in the TUI)
|
||||
const values = Computed.make<readonly A[]>((previous) => {
|
||||
const next = slots().map((slot) => slot()) // aggregate channel, lazy until subscribed
|
||||
const next = slots().map((slot) => slot()) // aggregate channel, lazy until subscribed
|
||||
return same(previous, next) ? previous! : next
|
||||
})
|
||||
|
||||
|
|
@ -135,11 +137,11 @@ export function make<A, Key>(options: {
|
|||
values,
|
||||
// ...
|
||||
update(value) {
|
||||
const key = options.key(value) // 1. one key extraction (a field read: row.id)
|
||||
const slot = byKey.get(key) // 2. one Map lookup — an address, not a search
|
||||
const key = options.key(value) // 1. one key extraction (a field read: row.id)
|
||||
const slot = byKey.get(key) // 2. one Map lookup — an address, not a search
|
||||
if (!slot) throw new Error(`Keyed value does not exist: ${String(key)}`)
|
||||
if (equivalent(slot(), value)) return false // 3. one equivalence check — early cutoff
|
||||
slot.set(value) // 4. one signal write; slots is untouched
|
||||
slot.set(value) // 4. one signal write; slots is untouched
|
||||
return true
|
||||
},
|
||||
// ...
|
||||
|
|
@ -169,20 +171,21 @@ const groupSlot = structure[0]
|
|||
|
||||
rows.update({ ...initial, completed: true })
|
||||
|
||||
rows.slots() === structure // true — <For> receives no change at all
|
||||
rows.slots()[0] === groupSlot // true — component owner survives
|
||||
groupSlot().completed === true // true — the one subscribed row re-renders
|
||||
rows.slots() === structure // true — <For> receives no change at all
|
||||
rows.slots()[0] === groupSlot // true — component owner survives
|
||||
groupSlot().completed === true // true — the one subscribed row re-renders
|
||||
```
|
||||
|
||||
This is also the **beauty** mechanism, not just speed: an expanded reasoning group keeps its local state through permission repartitioning, because refs moving between `refs` and `pending` is a value change on a stable identity — never a remount.
|
||||
|
||||
### The membership cutoff — duplicate deltas avoid the aggregate entirely
|
||||
|
||||
A route-scoped set contains every visible part identity, including refs nested
|
||||
inside groups. A duplicate streaming delta dies at `seenParts.has(id)` before
|
||||
reading `state.values()`. Duplicate message and footer events use `Keyed.has`
|
||||
against the collection's existing key map. For a replacement that reaches
|
||||
`update`, declared equivalence remains the final publication cutoff.
|
||||
The compiled `parts` members index contains every visible part identity,
|
||||
including refs nested inside groups. A duplicate streaming delta dies at
|
||||
`state.hasMember("parts", id)` before reading `state.values()`. Duplicate
|
||||
message and footer events use `Keyed.has` against the collection's existing key
|
||||
map. For a replacement that reaches `modify`, generated equivalence remains the
|
||||
final publication cutoff.
|
||||
|
||||
## More Before / After, From the Actual Diff
|
||||
|
||||
|
|
@ -226,12 +229,12 @@ mutate(() => {
|
|||
if (state.has(footerRowID(messageID))) return
|
||||
const current = state.values()
|
||||
const index = queuedStart(current)
|
||||
complete(current, index) // one state.update on the previous group, if open
|
||||
complete(current, index) // one state.update on the previous group, if open
|
||||
insert(current, index, footerRow(messageID)) // one new slot + one structural publication
|
||||
})
|
||||
```
|
||||
|
||||
Same policy, but the after version *names* its effects: at most one slot value change plus one structural change, flushed together by `Transaction.run`. Nothing has to diff anything to figure that out afterward.
|
||||
Same policy, but the after version _names_ its effects: at most one slot value change plus one structural change, flushed together by `Transaction.run`. Nothing has to diff anything to figure that out afterward.
|
||||
|
||||
### Removing a row
|
||||
|
||||
|
|
@ -260,18 +263,18 @@ setRows(reconcile(reduce()))
|
|||
setRows(reduce()) // → state.set(next): Map lookups + sameRow checks; unchanged rows publish nothing
|
||||
```
|
||||
|
||||
This is the one place the two systems do comparable O(N) work — and it's exactly the workload the checked benchmark measured. Every path above it is where Quark does structurally *less*.
|
||||
This is the one place the two systems do comparable O(N) work — and it's exactly the workload the checked benchmark measured. Every path above it is where Quark does structurally _less_.
|
||||
|
||||
## The Cost Ledger
|
||||
|
||||
| Per operation | Solid Store (`produce`/`reconcile`) | Quark `Keyed` |
|
||||
| --- | --- | --- |
|
||||
| Find the row | proxied array walk / identity re-derivation | one `Map.get` |
|
||||
| Detect "no change" | proxy-graph diff per touched path | one `equivalent()` call |
|
||||
| Value change | proxy writes + per-path invalidation | one signal write → one Solid signal |
|
||||
| Structure unchanged | reconcile still walks the list | outer array identity preserved; `<For>` silent |
|
||||
| Structure changed | full reconcile | one new array; retained slots reused by reference |
|
||||
| Batch of writes | store batching | one `Transaction.run` — settled publication |
|
||||
| Per operation | Solid Store (`produce`/`reconcile`) | Quark `Keyed` |
|
||||
| ------------------- | ------------------------------------------- | ------------------------------------------------- |
|
||||
| Find the row | proxied array walk / identity re-derivation | one `Map.get` |
|
||||
| Detect "no change" | proxy-graph diff per touched path | one `equivalent()` call |
|
||||
| Value change | proxy writes + per-path invalidation | one signal write → one Solid signal |
|
||||
| Structure unchanged | reconcile still walks the list | outer array identity preserved; `<For>` silent |
|
||||
| Structure changed | full reconcile | one new array; retained slots reused by reference |
|
||||
| Batch of writes | store batching | one `Transaction.run` — settled publication |
|
||||
|
||||
Same asymptotics — `O(N)` for a whole-array `set` — but far fewer instructions, allocations, and invalidations per unit of change. **The speedup is a constant-factor win purchased with a stronger contract** (unique stable keys, declared equivalence), not framework magic. The checked-in claim: ~4.7× on sparse value publication, ~6.4× on reorder, at 1,000 rows.
|
||||
|
||||
|
|
@ -281,7 +284,7 @@ Same asymptotics — `O(N)` for a whole-array `set` — but far fewer instructio
|
|||
|
||||
- **Deep module, tiny surface.** One small `keyed.ts` module carries the whole idea; the laws (unique key, stable key, equivalence, structural cutoff, settled publication, ownership) are explicit and testable.
|
||||
- **The adapter is honest.** `useValue` is 8 lines and creates no second reconciliation system — the failure mode the design doc itself warns about.
|
||||
- **Identity-as-input is the right call** for this domain: OpenCode *has* real identities (messageID, partID) and was previously throwing that information away for Solid to rediscover.
|
||||
- **Identity-as-input is the right call** for this domain: OpenCode _has_ real identities (messageID, partID) and was previously throwing that information away for Solid to rediscover.
|
||||
- **Falsifiable framing.** The docs predict where Quark should lose. That's rare and worth preserving.
|
||||
|
||||
### Where the architecture leaked — all fixed during review
|
||||
|
|
@ -292,7 +295,7 @@ Every leak identified in the first pass has since been closed:
|
|||
- ~~**The aggregate channel reintroduces O(N) per delta.**~~ `boundaries` now derives from the structure channel (`rows.slots()`) and reads slot values untracked (quark reads are invisible to Solid's tracker); the raw `values` readable is no longer mirrored into Solid. Per-delta boundary cost is zero; the O(N) recompute fires only on structural change or tracked `messages()` field changes.
|
||||
- ~~**Boundary staleness as an implicit invariant.**~~ The untracked-read trick is now type-enforced: `messageBoundaryIDs` accepts `readonly BoundaryRow[]`, a projection of `Pick`ed identity-immutable fields, so a future dependence on a mutable row field is a compile error.
|
||||
- ~~**Two grouping engines.**~~ Join-vs-insert policy is unified in `appendDecision` + `groupKind`; both the batch `reduce()` path and the incremental `appendPart` path consume it, and row construction goes through shared smart constructors.
|
||||
- ~~**Benchmark not checked in / wrong hot path.**~~ `packages/quark/bench/` now exists and covers incremental `keyed.update` — including against Solid's *direct path write*, the adversarial case — plus dense updates and reorders.
|
||||
- ~~**Benchmark not checked in / wrong hot path.**~~ `packages/quark/bench/` now exists and covers incremental `keyed.update` — including against Solid's _direct path write_, the adversarial case — plus dense updates and reorders.
|
||||
- ~~**No work counters.**~~ `Keyed` accepts an optional `Metrics` sink; `OPENCODE_QUARK_METRICS=1` reports timeline counters on cleanup.
|
||||
- ~~**Opaque positioning and adapter boilerplate.**~~ `insert` and `move` now share an explicit `Position` vocabulary (`"end"`, `before`, or `after`), `get` exposes stable slot addresses, and `KeyedFor` encapsulates the two-level Solid subscription.
|
||||
|
||||
|
|
|
|||
|
|
@ -80,37 +80,45 @@ recover identity and preserve nested owners.
|
|||
|
||||
## Quark Makes Identity an Input
|
||||
|
||||
`Keyed` receives the identity and equivalence functions when the collection is
|
||||
created. In the TUI, every row carries a collision-safe primitive `id`. Groups
|
||||
`Layout.collection` compiles identity, equivalence, and indexes once when the
|
||||
timeline is created. Every row carries a collision-safe primitive `id`. Groups
|
||||
also retain their immutable origin for message-boundary projection:
|
||||
|
||||
```typescript
|
||||
type PartRef = {
|
||||
messageID: string
|
||||
partID: string
|
||||
}
|
||||
const PartRefLayout = Layout.struct({
|
||||
messageID: Layout.string,
|
||||
partID: Layout.string,
|
||||
})
|
||||
|
||||
type SessionRow = { readonly id: string } & (
|
||||
| { type: "message"; messageID: string }
|
||||
| { type: "compaction-queued"; inputID: string }
|
||||
| { type: "part"; ref: PartRef }
|
||||
| {
|
||||
type: "group"
|
||||
kind: "reasoning"
|
||||
origin: PartRef
|
||||
refs: PartRef[]
|
||||
completed: boolean
|
||||
}
|
||||
| {
|
||||
type: "group"
|
||||
kind: "exploration"
|
||||
origin: PartRef
|
||||
refs: PartRef[]
|
||||
pending: PartRef[]
|
||||
completed: boolean
|
||||
}
|
||||
| { type: "assistant-footer"; messageID: string }
|
||||
)
|
||||
const SessionRowLayout = Layout.keyedUnion({
|
||||
key: Layout.key("id", Layout.string),
|
||||
tag: "type",
|
||||
variants: {
|
||||
message: Layout.struct({ messageID: Layout.string }),
|
||||
"compaction-queued": Layout.struct({ inputID: Layout.string }),
|
||||
part: Layout.struct({ ref: PartRefLayout }),
|
||||
group: Layout.union({
|
||||
tag: "kind",
|
||||
variants: {
|
||||
reasoning: Layout.struct({
|
||||
origin: Layout.immutable(PartRefLayout),
|
||||
refs: Layout.array(PartRefLayout),
|
||||
completed: Layout.boolean,
|
||||
}),
|
||||
exploration: Layout.struct({
|
||||
origin: Layout.immutable(PartRefLayout),
|
||||
refs: Layout.array(PartRefLayout),
|
||||
pending: Layout.array(PartRefLayout),
|
||||
completed: Layout.boolean,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
"assistant-footer": Layout.struct({ messageID: Layout.string }),
|
||||
},
|
||||
})
|
||||
|
||||
type PartRef = Layout.Type<typeof PartRefLayout>
|
||||
type SessionRow = Layout.Type<typeof SessionRowLayout>
|
||||
```
|
||||
|
||||
Factories build the ID once with length-prefixed segments. Length prefixes
|
||||
|
|
@ -126,56 +134,28 @@ function groupRow(kind: "reasoning" | "exploration", origin: PartRef): SessionRo
|
|||
if (kind === "reasoning") return { id, type: "group", kind, origin, refs: [origin], completed: false }
|
||||
return { id, type: "group", kind, origin, refs: [origin], pending: [], completed: false }
|
||||
}
|
||||
|
||||
function rowKey(row: SessionRow) {
|
||||
return row.id
|
||||
}
|
||||
```
|
||||
|
||||
`sameRow` answers a different question: can consumers distinguish these two
|
||||
values for the same identity? It compares only fields that affect row
|
||||
rendering.
|
||||
Generated equivalence answers a different question: can consumers distinguish
|
||||
these two values for the same identity? The layout compares rendered fields,
|
||||
dispatches through the row and group tags, and ignores immutable `origin`.
|
||||
|
||||
```typescript
|
||||
function sameRow(left: SessionRow, right: SessionRow) {
|
||||
if (left.type !== right.type) return false
|
||||
const SessionRows = Layout.collection(
|
||||
SessionRowLayout,
|
||||
({ members }) => ({
|
||||
parts: members(partIDs),
|
||||
}),
|
||||
{ backend: "generated" },
|
||||
)
|
||||
|
||||
if (left.type === "message" && right.type === "message") return left.messageID === right.messageID
|
||||
|
||||
if (left.type === "compaction-queued" && right.type === "compaction-queued") return left.inputID === right.inputID
|
||||
|
||||
if (left.type === "part" && right.type === "part") return sameRef(left.ref, right.ref)
|
||||
|
||||
if (left.type === "assistant-footer" && right.type === "assistant-footer") return left.messageID === right.messageID
|
||||
|
||||
if (left.type !== "group" || right.type !== "group") return false
|
||||
if (left.kind !== right.kind) return false
|
||||
if (left.completed !== right.completed) return false
|
||||
if (!sameRefs(left.refs, right.refs)) return false
|
||||
|
||||
if (left.kind === "reasoning" || right.kind === "reasoning") return true
|
||||
return sameRefs(left.pending, right.pending)
|
||||
}
|
||||
|
||||
function sameRefs(left: PartRef[], right: PartRef[]) {
|
||||
return left.length === right.length && left.every((ref, index) => sameRef(ref, right[index]))
|
||||
}
|
||||
|
||||
function sameRef(left: PartRef, right: PartRef) {
|
||||
return left.messageID === right.messageID && left.partID === right.partID
|
||||
}
|
||||
const rows = SessionRows.make()
|
||||
```
|
||||
|
||||
Those functions form the complete reconciliation policy:
|
||||
|
||||
```typescript
|
||||
const rows = Keyed.make<SessionRow, string>({
|
||||
key: rowKey,
|
||||
equivalent: sameRow,
|
||||
})
|
||||
|
||||
rows.set(nextRows)
|
||||
```
|
||||
The `parts` index contains standalone and grouped part IDs. Duplicate stream
|
||||
deltas return through `hasMember` before aggregate projection. Arbitrary
|
||||
replacements derive membership automatically; the hot group-append path applies
|
||||
the exact one-member delta already known from the event.
|
||||
|
||||
It exposes two reactive surfaces:
|
||||
|
||||
|
|
@ -207,10 +187,7 @@ rows.set([initial])
|
|||
const structure = rows.slots()
|
||||
const groupSlot = structure[0]
|
||||
|
||||
rows.update({
|
||||
...initial,
|
||||
completed: true,
|
||||
})
|
||||
rows.modify(initial.id, (group) => ({ ...group, completed: true }))
|
||||
|
||||
rows.slots() === structure // true: <For> receives no structural change
|
||||
rows.slots()[0] === groupSlot // true: component owner survives
|
||||
|
|
@ -269,13 +246,14 @@ The current algorithm is deliberately small:
|
|||
```text
|
||||
set(next):
|
||||
1. Compute every next key and reject duplicates.
|
||||
2. Build Map<key, previousSlot>.
|
||||
2. Look up retained slots in the persistent key map.
|
||||
3. For each next value:
|
||||
a. Reuse the previous slot for its key, or create one.
|
||||
b. Compare previous and next values.
|
||||
b. Run generated equivalence over previous and next values.
|
||||
c. Write only a changed slot.
|
||||
4. Publish the outer slot array only if membership or order changed.
|
||||
5. Flush slot and structural writes in one transaction.
|
||||
5. Synchronize declared collection indexes.
|
||||
6. Flush slot, index, and structural writes in one transaction.
|
||||
```
|
||||
|
||||
The aggregate `values` readable maps the current slots to their values. It is a
|
||||
|
|
@ -322,54 +300,31 @@ These laws are stronger than a plain `Array<object>` contract. They are also
|
|||
testable. Quark should reject a violated unique-key law and should have direct
|
||||
tests for every other law.
|
||||
|
||||
## Fixed Layout Research Is Deferred
|
||||
## Layout Compiles Trusted Collection Metadata
|
||||
|
||||
The standalone laboratory applies the same idea to records. The initial prototype
|
||||
used Effect Schema, but its derived equivalence measured `1.621x` the hand
|
||||
comparator's direct-update cost. Quark now experiments with a smaller trusted
|
||||
in-memory `Layout` that compiles:
|
||||
The timeline now uses Quark's trusted in-memory `Layout`. It supports the scope
|
||||
required by this experiment:
|
||||
|
||||
- field names into numeric positions;
|
||||
- field changes into a bit mask;
|
||||
- field equivalence into precomputed functions;
|
||||
- entity keys and indexed fields into fixed metadata.
|
||||
- primitive, array, struct, discriminated-union, and keyed-union fields;
|
||||
- immutable fields excluded from rendered equivalence;
|
||||
- compiled key extraction and closure or generated equivalence;
|
||||
- imperative multi-value membership and ordered first-match indexes;
|
||||
- automatic index derivation for arbitrary replacements;
|
||||
- typed member deltas for event-native mutations that know exact changes.
|
||||
|
||||
The minimal API marks the key in the shape itself:
|
||||
Generated nested union comparison measured `1.06x-1.10x` handwritten cost,
|
||||
versus `1.19x-1.22x` for closure Layout across three runs. Generation occurs
|
||||
once when `SessionRows` is declared. The older `1.322x` closure result motivated
|
||||
this backend; it no longer describes the production comparator.
|
||||
|
||||
```typescript
|
||||
const ItemLayout = Layout.struct({
|
||||
id: Layout.key(Layout.number),
|
||||
value: Layout.number,
|
||||
})
|
||||
Automatic membership derivation is linear in an affected row's members. That
|
||||
is the safe default, but it made repeated append to one growing group roughly
|
||||
`27x-30x` the handwritten index path. The event already identifies the new
|
||||
part, so `SessionTimeline.appendPart` supplies a typed one-member delta. Generic
|
||||
completion and permission repartition retain automatic derivation.
|
||||
|
||||
const ItemPlan = Layout.compile(ItemLayout)
|
||||
const items = ItemPlan.make([
|
||||
{ id: 1, value: 10 },
|
||||
{ id: 2, value: 20 },
|
||||
])
|
||||
|
||||
items.update({ id: 2, value: 21 })
|
||||
```
|
||||
|
||||
`ItemPlan` exposes the original field metadata, compiled key field, compiled
|
||||
equivalence, and collection factory. Simple struct comparison has measured
|
||||
near handwritten speed, but a representative nested session-row union measured
|
||||
`1.322x` the handwritten keyed-update cost after closure specialization.
|
||||
Generated comparison can remove more indirection, but it is optional research,
|
||||
not a requirement for this timeline experiment.
|
||||
|
||||
A one-field record replacement can first compute the change mask, then write
|
||||
only changed field slots. An indexed-field write knows exactly which index
|
||||
buckets to remove from and add to. An entity upsert resolves directly through
|
||||
its immutable key.
|
||||
|
||||
The static information is therefore not “the compiler knows the type.” It is
|
||||
“the runtime has already compiled the layout and the application has agreed to
|
||||
obey its laws.” Effect Schema can still validate or transform data at an
|
||||
admission boundary without participating in collection updates. None of this
|
||||
`Layout`, model, mask, index, or columnar-storage machinery is vendored into the
|
||||
TUI fork. The current landing remains the small `Keyed` abstraction plus the
|
||||
route reducer; broader promotion requires separate evidence.
|
||||
Numeric field positions, change masks, per-field reactive slots, models, and
|
||||
columnar storage remain future research. They are not required by the TUI.
|
||||
|
||||
## Asymptotics and Constants
|
||||
|
||||
|
|
@ -388,11 +343,12 @@ complexity**:
|
|||
- no general nested proxy reconciliation inside Quark.
|
||||
|
||||
For direct collection operations, stronger bounds are possible. An immutable
|
||||
key lets `Keyed.update` or `Collection.upsert` find an entity in expected `O(1)`
|
||||
time, after which work is proportional to changed fields and affected declared
|
||||
indexes rather than total collection size. The TUI now uses event-native
|
||||
`update`, `insert`, and `remove` operations for live events; full synchronization
|
||||
and revert rebuilds still use linear `set`.
|
||||
key lets `has`, `get`, `hasMember`, and `modify` find their target in expected
|
||||
`O(1)` time. Work is then proportional to the changed row, affected declared
|
||||
indexes, and any ordered structural array copy. `SessionTimeline` caches the
|
||||
active group and queued boundary IDs; inserts and removals remain `O(N)` because
|
||||
the ordered slots array must be copied. Full synchronization and revert rebuilds
|
||||
remain linear `set` operations.
|
||||
|
||||
## Controlled Benchmark Evidence
|
||||
|
||||
|
|
@ -402,6 +358,9 @@ The benchmarks are checked into the fork and run directly:
|
|||
cd packages/quark
|
||||
bun run bench:keyed
|
||||
bun run bench:row-key
|
||||
|
||||
cd ../tui
|
||||
bun run bench:timeline
|
||||
```
|
||||
|
||||
Each invocation interleaves and rotates variants over nine measured samples
|
||||
|
|
@ -425,20 +384,21 @@ Across these two invocations:
|
|||
The checksum was stable, and both variants performed the same next-array
|
||||
construction inside their timed workloads.
|
||||
|
||||
The refreshed integration benchmark measures event-native updates with the
|
||||
aggregate `values` channel subscribed, plus adverse workloads. The July 17,
|
||||
2026 evidence run produced:
|
||||
The refreshed lower-level `Keyed` benchmark measures event-native updates with
|
||||
the aggregate `values` channel subscribed, plus adverse workloads. It does not
|
||||
include `SessionTimeline`, generated row equivalence, or compiled index
|
||||
maintenance. The July 17, 2026 evidence run produced:
|
||||
|
||||
| Workload | Quark | Solid | Quark / Solid |
|
||||
| ----------------------------------------- | ----------: | ----------: | ------------: |
|
||||
| Direct update, no subscribers, 1,000 | 46.8 ns | 761.6 ns | 0.068x |
|
||||
| Precise Solid path write, 1,000 | 54.3 ns | 327.5 ns | 0.170x |
|
||||
| Subscribed aggregate, 10 rows | 608.0 ns | 7.4 us | 0.085x |
|
||||
| Subscribed aggregate, 100 rows | 4.4 us | 80.0 us | 0.055x |
|
||||
| Subscribed aggregate, 1,000 rows | 37.4 us | 703.1 us | 0.052x |
|
||||
| Subscribed aggregate, 10,000 rows | 388.5 us | 8.1 ms | 0.044x |
|
||||
| Dense 1,000-row update | 270.2 us | 2.3 ms | 0.132x |
|
||||
| Unstable keys, 100 rows | 68.0 us | 245.2 us | 0.282x |
|
||||
| Workload | Quark | Solid | Quark / Solid |
|
||||
| ------------------------------------ | -------: | -------: | ------------: |
|
||||
| Direct update, no subscribers, 1,000 | 46.8 ns | 761.6 ns | 0.068x |
|
||||
| Precise Solid path write, 1,000 | 54.3 ns | 327.5 ns | 0.170x |
|
||||
| Subscribed aggregate, 10 rows | 608.0 ns | 7.4 us | 0.085x |
|
||||
| Subscribed aggregate, 100 rows | 4.4 us | 80.0 us | 0.055x |
|
||||
| Subscribed aggregate, 1,000 rows | 37.4 us | 703.1 us | 0.052x |
|
||||
| Subscribed aggregate, 10,000 rows | 388.5 us | 8.1 ms | 0.044x |
|
||||
| Dense 1,000-row update | 270.2 us | 2.3 ms | 0.132x |
|
||||
| Unstable keys, 100 rows | 68.0 us | 245.2 us | 0.282x |
|
||||
|
||||
A second independent invocation produced paired ratios of `0.084x`,
|
||||
`0.170x`, `0.077x`, `0.059x`, `0.061x`, `0.042x`, `0.130x`, and `0.266x`
|
||||
|
|
@ -455,6 +415,24 @@ falsified prediction for this setup, not evidence that Solid can never win a
|
|||
direct-write comparison. The production TUI has no aggregate subscriber; it
|
||||
tracks structural slots and immutable boundary metadata instead.
|
||||
|
||||
The checked-in `SessionTimeline` benchmark compares the final domain module to
|
||||
the previous handwritten `Keyed + Set` reducer mechanics and the original Solid
|
||||
Store `produce` path. Across three runs:
|
||||
|
||||
| Workload | SessionTimeline result |
|
||||
| ----------------------------------------------------- | ----------------------: |
|
||||
| Growing reasoning-group append / handwritten Quark | about `1.3x-1.5x` |
|
||||
| Growing reasoning-group append / Solid `produce` | about `0.013x` |
|
||||
| Duplicate part in a 1,000-ref group / Solid `produce` | about `0.0004x-0.0005x` |
|
||||
|
||||
The final module pays roughly 30%-50% over the minimal handwritten Quark path
|
||||
for compiled layout dispatch, domain policy, and maintained index ownership.
|
||||
It remains roughly 74x-79x faster than the baseline Solid append in this
|
||||
growing-group stress workload. Duplicate lookup is expected `O(1)` through the
|
||||
compiled members index; the Solid baseline scans 1,000 refs, producing an
|
||||
approximately 1,960x-2,500x stress-case difference. These are state-operation
|
||||
results, not claims about total OpenCode response latency.
|
||||
|
||||
Key extraction was measured independently over 10,000 representative rows:
|
||||
|
||||
| Key strategy | Median | Paired ratio to JSON |
|
||||
|
|
@ -468,8 +446,8 @@ The TUI now uses the precomputed primitive ID strategy.
|
|||
## What the Benchmark Does Not Prove
|
||||
|
||||
The suite compares whole-array reconciliation, direct point updates, subscribed
|
||||
aggregate projection, dense changes, unstable keys, and key extraction. It
|
||||
does not prove that Quark beats:
|
||||
aggregate projection, dense changes, unstable keys, key extraction, growing
|
||||
timeline groups, and duplicate deltas. It does not prove that Quark beats:
|
||||
|
||||
- every Solid keyed-list primitive;
|
||||
- rendering, terminal layout, or paint;
|
||||
|
|
@ -500,19 +478,18 @@ These are falsifiable predictions. The current adverse suite did not find a
|
|||
Solid win, including the newly added precise path write, but it preserves these
|
||||
cases so future changes cannot optimize only the favorable sparse path.
|
||||
|
||||
## The Next Tests Must Measure Work, Not Just Time
|
||||
## Deterministic Tests Measure Work, Not Just Time
|
||||
|
||||
The strongest next experiment is a single-binary A/B implementation driven by
|
||||
one deterministic event trace. It should count:
|
||||
The single-binary deterministic event trace counts:
|
||||
|
||||
| Deterministic transition | Slot delta | Structure delta | Observed ownership behavior |
|
||||
| ------------------------------ | ---------: | --------------: | ------------------------------------------- |
|
||||
| First exploration part | +0 | +1 | New group slot |
|
||||
| Extend exploration group | +1 | +0 | Existing group slot retained |
|
||||
| Permission repartition | +1 | +0 | Existing group slot retained |
|
||||
| Insert queued user row | +0 | +1 | Group remains mounted and incomplete |
|
||||
| Complete promoted-input group | +1 | +0 | Existing group slot retained |
|
||||
| Duplicate text delta | +0 | +0 | Returns before aggregate materialization |
|
||||
| Deterministic transition | Slot delta | Structure delta | Observed ownership behavior |
|
||||
| ------------------------------ | ---------: | --------------: | -------------------------------------------- |
|
||||
| First exploration part | +0 | +1 | New group slot |
|
||||
| Extend exploration group | +1 | +0 | Existing group slot retained |
|
||||
| Permission repartition | +1 | +0 | Existing group slot retained |
|
||||
| Insert queued user row | +0 | +1 | Group remains mounted and incomplete |
|
||||
| Complete promoted-input group | +1 | +0 | Existing group slot retained |
|
||||
| Duplicate text delta | +0 | +0 | Returns through compiled membership index |
|
||||
| Full unchanged two-row rebuild | +0 | +0 | Two equivalence suppressions, no publication |
|
||||
|
||||
`Keyed` exposes optional counters for slot publications, structural
|
||||
|
|
@ -524,6 +501,11 @@ deltas` records the duplicate path. The direct Keyed law test records the full
|
|||
unchanged rebuild. Boundary tests independently assert that value-only changes
|
||||
do not invalidate structural boundary projection.
|
||||
|
||||
Pure timeline tests additionally cover cached queue advancement, out-of-order
|
||||
promotion, duplicate message/footer idempotence, stable group ownership, and
|
||||
parity between event-native operations and snapshot reduction. Unique append
|
||||
uses cached IDs and never reads `state.values()`.
|
||||
|
||||
## Decision Rule
|
||||
|
||||
Continue the Quark timeline experiment if deterministic replay confirms all of
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ Status: experiment in progress
|
|||
|
||||
## Summary
|
||||
|
||||
The V2 TUI currently stores its rendered session-row list in a Solid Store.
|
||||
This experiment changes only that owner: `createSessionRows` stores an ordered
|
||||
array of stable row slots in Quark state. Each slot owns one `SessionRow`, and
|
||||
an owner-aware adapter exposes both levels to the existing Solid renderer.
|
||||
The V2 TUI previously stored its rendered session-row list in a Solid Store.
|
||||
This experiment changes only that owner: `SessionTimeline` stores an ordered
|
||||
array of stable row slots in a compiled Quark collection. Each slot owns one
|
||||
`SessionRow`, and an owner-aware adapter exposes both levels to the existing
|
||||
Solid renderer.
|
||||
|
||||
The event protocol, durable session data, `DataProvider`, row reduction rules,
|
||||
and `SessionRowView` remain unchanged. This boundary makes the experiment easy
|
||||
to compare and easy to remove. It does not yet move messages or secondary
|
||||
indexes into Quark collections.
|
||||
to compare and easy to remove. Message content remains in `DataProvider`;
|
||||
`SessionTimeline` owns a finite imperative index of part IDs for its current
|
||||
rows.
|
||||
|
||||
The experiment succeeds only if all three checks pass:
|
||||
|
||||
|
|
@ -56,10 +58,13 @@ server events
|
|||
DataProvider Solid Store
|
||||
|
|
||||
v
|
||||
createSessionRows
|
||||
createSessionRows adapter
|
||||
|
|
||||
v
|
||||
Quark State<RowSlot[]>
|
||||
SessionTimeline
|
||||
|
|
||||
v
|
||||
Layout.collection<SessionRow>
|
||||
|
|
||||
v
|
||||
KeyedFor outer + slot adapters
|
||||
|
|
@ -74,15 +79,17 @@ would make a regression or improvement impossible to attribute.
|
|||
|
||||
## Components and Responsibilities
|
||||
|
||||
| Component | Responsibility |
|
||||
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `packages/tui/src/context/data.tsx` | Preserve the existing server-event projection and message lookup API. |
|
||||
| `packages/tui/src/routes/session/rows.ts` | Reduce loaded messages, apply incremental timeline events, and own ordered rows. |
|
||||
| `packages/quark/src/reactivity.ts` | Provide synchronous state updates, subscriptions, and transaction batching. |
|
||||
| `packages/quark/src/keyed.ts` | Reuse per-key slots and separate value changes from structural changes. |
|
||||
| `packages/quark/src/solid.ts` | Bridge Quark readables into Solid ownership and compose stable slots with `KeyedFor`. |
|
||||
| `packages/tui/src/routes/session/index.tsx` | Render stable row accessors through `KeyedFor` and the existing `SessionRowView` components. |
|
||||
| `script/quark-timeline-drive.ts` | Exercise the same streamed timeline scenario against baseline and fork binaries. |
|
||||
| Component | Responsibility |
|
||||
| --------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| `packages/tui/src/context/data.tsx` | Preserve the existing server-event projection and message lookup API. |
|
||||
| `packages/tui/src/routes/session/rows.ts` | Translate DataProvider state and events into timeline operations; own the Solid adapter lifecycle. |
|
||||
| `packages/tui/src/routes/session/timeline.ts` | Declare row layout, reduce snapshots, cache cursors, and implement timeline domain mutations. |
|
||||
| `packages/quark/src/layout.ts` | Compile row keys/equivalence and maintain declared imperative collection indexes. |
|
||||
| `packages/quark/src/reactivity.ts` | Provide synchronous state updates, subscriptions, and transaction batching. |
|
||||
| `packages/quark/src/keyed.ts` | Reuse per-key slots and separate value changes from structural changes. |
|
||||
| `packages/quark/src/solid.ts` | Bridge Quark readables into Solid ownership and compose stable slots with `KeyedFor`. |
|
||||
| `packages/tui/src/routes/session/index.tsx` | Render stable row accessors through `KeyedFor` and the existing `SessionRowView` components. |
|
||||
| `script/quark-timeline-drive.ts` | Exercise the same streamed timeline scenario against baseline and fork binaries. |
|
||||
|
||||
## Quark State Publishes One Synchronous Value
|
||||
|
||||
|
|
@ -187,10 +194,11 @@ Most live events update the current row array directly:
|
|||
| Step started | Remove the previous retry footer. |
|
||||
| Permission changed | Repartition exploration refs between visible and pending lists. |
|
||||
|
||||
`queuedStart(rows)` finds the first queued compaction or pending message. New
|
||||
assistant parts and completed messages insert before that boundary. Pending
|
||||
user inputs append after queued work. A running compaction inserts at the start
|
||||
of the queued region.
|
||||
`SessionTimeline` caches the first queued row ID and the active incomplete group
|
||||
ID. Full replacement derives both once. Live operations maintain them as work
|
||||
is queued or promoted. New assistant parts and completed messages insert before
|
||||
the cached queue boundary; pending user inputs append after queued work; a
|
||||
running compaction becomes the new boundary.
|
||||
|
||||
`append(rows, ref, part, index)` implements grouping:
|
||||
|
||||
|
|
@ -214,13 +222,14 @@ because call IDs can look like generated text or reasoning IDs.
|
|||
|
||||
The old Solid `produce` path created a mutable draft of the row list. The first
|
||||
Quark implementation also rebuilt a complete next array, then reconciled it.
|
||||
The event-native path now uses the information already present in each event:
|
||||
The event-native path now calls domain operations backed by the compiled
|
||||
collection:
|
||||
|
||||
```text
|
||||
value change -> Keyed.update(row)
|
||||
new row -> Keyed.insert(row, { before })
|
||||
removed footer -> Keyed.remove(row.id)
|
||||
full sync / revert -> Keyed.set(nextRows)
|
||||
group change -> state.modify(groupID, update)
|
||||
new row -> state.insert(row, { before })
|
||||
removed footer -> state.remove(row.id)
|
||||
full sync / revert -> state.set(nextRows)
|
||||
```
|
||||
|
||||
Extending or completing a group creates one new group value with copied refs
|
||||
|
|
@ -229,9 +238,11 @@ structure. Permission repartitioning first checks whether a group's visible or
|
|||
pending membership would actually change, then allocates arrays only for those
|
||||
groups.
|
||||
|
||||
Duplicate message, part, or footer events return before a Keyed operation.
|
||||
Same-ordinal streaming deltas therefore produce no slot or structural
|
||||
publication after the first part.
|
||||
Message and footer duplicates use the keyed map. Part duplicates use the
|
||||
compiled `parts` members index and return before aggregate projection. Unique
|
||||
group appends use cached IDs and do not read `state.values()`. The append event
|
||||
also supplies the exact one-member index delta, avoiding a scan of the growing
|
||||
group; arbitrary replacements retain automatic index derivation.
|
||||
|
||||
## `Keyed` Preserves Row Identity
|
||||
|
||||
|
|
@ -240,24 +251,28 @@ to preserve child ownership. A changed group value must not remount its
|
|||
`SessionRowView`, because that would reset the group's local expanded and hover
|
||||
state. `<For>` therefore receives stable Quark slots rather than row values.
|
||||
|
||||
`Keyed.set(nextRows)` preserves ownership in four passes:
|
||||
`Keyed.set(nextRows)` preserves ownership through its persistent key-to-slot
|
||||
map:
|
||||
|
||||
```text
|
||||
1. Index previous slots by the semantic key of their current row.
|
||||
2. For each next row, find the previous slot with that key.
|
||||
3. Update that slot only when the rendered row fields changed.
|
||||
1. Validate the next keys.
|
||||
2. Find each retained slot through the persistent map.
|
||||
3. Update that slot only when generated row equivalence reports a change.
|
||||
4. Reuse the previous outer slot array when membership and order are unchanged.
|
||||
```
|
||||
|
||||
Every row receives one collision-safe length-prefixed primitive ID when it is
|
||||
created. `Keyed` reads `row.id`; reconciliation performs no serialization:
|
||||
created. `SessionRowLayout` declares that key and generates discriminated-union
|
||||
equivalence. Group `origin` is immutable metadata; `refs`, `pending`, and
|
||||
`completed` participate in equivalence. Reconciliation performs no
|
||||
serialization:
|
||||
|
||||
| Row | ID shape |
|
||||
| ----------------- | ----------------------------------------------------- |
|
||||
| Message | `m{length}:{messageID}` |
|
||||
| Queued compaction | `c{length}:{inputID}` |
|
||||
| Part | `p{message segment}{part segment}` |
|
||||
| Assistant footer | `f{length}:{messageID}` |
|
||||
| Row | ID shape |
|
||||
| ----------------- | ------------------------------------------------------ |
|
||||
| Message | `m{length}:{messageID}` |
|
||||
| Queued compaction | `c{length}:{inputID}` |
|
||||
| Part | `p{message segment}{part segment}` |
|
||||
| Assistant footer | `f{length}:{messageID}` |
|
||||
| Group | `g{kind}{origin message segment}{origin part segment}` |
|
||||
|
||||
Every group records the ref that created it as immutable `origin` metadata.
|
||||
|
|
@ -269,10 +284,13 @@ partitions cannot change its ID or boundary identity. Group equivalence compares
|
|||
order changes, so Solid `<For>` does no structural work for a group-value
|
||||
update. `values` depends on both the outer list and every slot for consumers
|
||||
that require aggregate values. The TUI has no reactive `values` subscriber; it
|
||||
uses the lazy aggregate only as an internal snapshot for unique mutation paths.
|
||||
uses the lazy aggregate for bulk permission repartitioning and snapshot/test
|
||||
consumers.
|
||||
Message boundaries track structural slots and messages, then read immutable
|
||||
row identity fields untracked. A changed group publishes through its existing
|
||||
slot and preserves the mounted Solid owner without recalculating boundaries.
|
||||
The lazy aggregate is read by permission repartitioning and snapshot/test
|
||||
consumers, not by unique append paths.
|
||||
|
||||
Slot updates and the outer-array update run inside both a Quark transaction and
|
||||
a Solid batch. The Quark transaction settles the alien-signals graph; the
|
||||
|
|
@ -285,23 +303,25 @@ from one ordering and an outer slot array from another.
|
|||
Let `R` be visible rows, `M` loaded messages, `P` assistant content parts, and
|
||||
`C` the total refs contained by visible groups.
|
||||
|
||||
| Operation | Time | Allocation |
|
||||
| -------------------------- | -----------------------: | ----------------------------------------------: |
|
||||
| Full rebuild | `O(M + P + R)` | New reduction plus reconciled slot array |
|
||||
| Unique incremental append | `O(R + C)` | One structural array or one changed group array |
|
||||
| Duplicate part append | `O(1)` | None |
|
||||
| Duplicate message/footer | `O(1)` | None |
|
||||
| Footer removal | `O(R)` | One structural slot array |
|
||||
| Permission repartition | `O(R + C)` | Arrays only for groups whose split moves |
|
||||
| Quark-to-Solid publication | `O(1)` per changed level | One slot write and, when required, outer write |
|
||||
| Operation | Time | Allocation |
|
||||
| ---------------------------- | -----------------------: | ---------------------------------------------: |
|
||||
| Full rebuild | `O(M + P + R)` | New reduction plus reconciled slot array |
|
||||
| Active group / queue lookup | Expected `O(1)` | None |
|
||||
| Queue boundary advancement | `O(R)` | None |
|
||||
| Group extension | `O(G)` | One copied group array; `O(1)` index delta |
|
||||
| Structural insertion/removal | `O(R)` | One structural slots array |
|
||||
| Duplicate part append | `O(1)` | One key string |
|
||||
| Duplicate message/footer | `O(1)` | One key string |
|
||||
| Footer removal | `O(R)` | One structural slot array |
|
||||
| Permission repartition | `O(R + C)` | Arrays only for groups whose split moves |
|
||||
| Quark-to-Solid publication | `O(1)` per changed level | One slot write and, when required, outer write |
|
||||
|
||||
`Keyed.has` handles message and footer membership through the collection's
|
||||
existing key map. A route-scoped `Set` contains every visible part ID, including
|
||||
refs nested inside groups. Full rebuilds repopulate it and unique appends add to
|
||||
it once. Duplicate streaming deltas therefore return before reading the lazy
|
||||
aggregate or scanning rows. Unique appends still materialize the current row
|
||||
snapshot and scan for the queued boundary; that lower-frequency path remains
|
||||
linear deliberately rather than introducing a secondary-index layer.
|
||||
existing key map. `Layout.collection` maintains every visible part ID,
|
||||
including refs nested inside groups. Full replacement rebuilds the finite index;
|
||||
inserts and arbitrary modifications derive membership automatically; the hot
|
||||
group append applies one typed member delta. Duplicate streaming deltas return
|
||||
before reading the lazy aggregate or scanning rows.
|
||||
|
||||
## Solid Owns the Adapter Lifecycle
|
||||
|
||||
|
|
@ -377,7 +397,7 @@ microbenchmark. Provider simulation, terminal polling, and process scheduling
|
|||
also contribute to it. A performance decision requires repeated paired runs
|
||||
and row-update or invalidation counters in addition to this behavioral check.
|
||||
|
||||
Three alternating smoke pairs completed successfully:
|
||||
Seven baseline/fork behavior pairs completed successfully:
|
||||
|
||||
| Pair | Baseline | Quark fork | Fork / baseline |
|
||||
| ---------------------: | --------: | ---------: | --------------: |
|
||||
|
|
@ -386,7 +406,8 @@ Three alternating smoke pairs completed successfully:
|
|||
| 3, fork first | 9,296 ms | 7,563 ms | 0.813x |
|
||||
| 4, API assertions | 4,283 ms | 3,114 ms | 0.727x |
|
||||
| 5, current `origin/v2` | 3,636 ms | 2,074 ms | 0.570x |
|
||||
| 6, current Drive API | 2,233 ms | 1,413 ms | 0.633x |
|
||||
| 6, current Drive API | 2,233 ms | 1,413 ms | 0.633x |
|
||||
| 7, timeline extraction | 973 ms | 825 ms | 0.848x |
|
||||
|
||||
The range is too wide to support a speed claim. These runs establish that the
|
||||
same streamed scenario renders and terminates on both implementations. They do
|
||||
|
|
@ -394,22 +415,22 @@ not establish that either implementation is faster.
|
|||
|
||||
## Validation Status
|
||||
|
||||
| Check | Current result |
|
||||
| ------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| TUI package typecheck | Pass |
|
||||
| Full TUI test suite | 267 pass, 1 skip |
|
||||
| Focused data and row tests | 48 pass |
|
||||
| Vendored Quark tests | 13 pass |
|
||||
| Shared drive scripts typecheck | Pass |
|
||||
| Shared Drive behavior | Six baseline/fork pairs pass; latest pair verifies projected content |
|
||||
| Publication-counter trace | Pass; exact slot/structure deltas recorded in performance model |
|
||||
| Paired performance evidence | Pass for controlled workloads; end-to-end wall time remains non-conclusive |
|
||||
| Check | Current result |
|
||||
| ------------------------------ | -------------------------------------------------------------------------- |
|
||||
| TUI package typecheck | Pass |
|
||||
| Full TUI test suite | 274 pass, 1 skip |
|
||||
| Focused data and row tests | 55 pass |
|
||||
| Vendored Quark tests | 24 pass |
|
||||
| Shared drive scripts typecheck | Pass |
|
||||
| Shared Drive behavior | Seven baseline/fork pairs pass; latest pair verifies projected content |
|
||||
| Publication-counter trace | Pass; exact slot/structure deltas recorded in performance model |
|
||||
| Paired performance evidence | Pass for controlled workloads; end-to-end wall time remains non-conclusive |
|
||||
|
||||
## Collection Cache Growth Is Outside This Phase
|
||||
## Reactive Query Caches Remain Outside This Phase
|
||||
|
||||
The standalone `effect-quark` prototype includes reactive missing-key and
|
||||
secondary-index query caches without eviction. This branch vendors only Quark
|
||||
state and the Solid adapter; it does not create those caches.
|
||||
The timeline's imperative members index is finite: entries follow current rows
|
||||
and are removed synchronously. It is not a reactive query cache and renderers
|
||||
do not subscribe to it.
|
||||
|
||||
A later message-collection phase must not delete empty cache entries while a
|
||||
live readable still references them. Safe eviction therefore needs explicit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue