Version. 0.1
Date. 2026-05-04
Author. Marvin Percival (DUNIN7), prepared via Claude.
Target. /Users/dunin7/loomworks (substrate) and /Users/dunin7/loomworks-ui (frontend) on DUNIN7-M4 (MacMini).
Priority. Standard.
Confidential. Internal.
Supersedes. Nothing for Phase 36 specifically.
Companion to. Phase 36 scoping note v0.2, engine implementation strategy v0.2 §6, Phase 17 CR v0.2 (display number pattern), Phase 35 CR v0.2 (carry-forward discipline), Phase 34 CR v0.2 (external dispatch).
Status. First draft. Consumes settled scoping decisions from scoping note v0.2 without relitigating them.
Phase 36 adds two features that became one during scoping.
Feature A — Navigable naming chain. Shapes and renders each receive a stable sequential display number, assigned at production time, scoped within (engagement_id, declared_type_ref). Numbers are never reused. The full navigable identity path from engagement to render reads:
> Manatee Lifestyle · M2 · habitat-guide #3 · visitor-brochure #2
The Operator named the engagement and the type declarations (in the seed). Everything else is system-assigned. Content-derived titles become enrichment alongside the display number, not identity. This extends the display number pattern Phase 17 established for assertions to the shape and render layers.
Feature B — Incremental re-render lineage. When a specialist has already produced a render for a declared render type and a new render is triggered, the engine can now hand the specialist both the new shape and the prior render. prior_render_ref on RenderEvent captures the lineage chain. revision_strategy distinguishes incremental from fresh re-renders. Specialists declare supports_incremental_rerender and implement a rerender() method. The engine orchestration branch dispatches to the right method. Lineage is recorded even for fresh re-renders when a prior render exists — the chain captures all renders, not just incremental ones.
Full-stack phase: substrate + frontend. Two checkpoints (A: substrate, B: frontend + tag).
Phase 36 is grounded in three layers, consulted in order.
Layer 1 — Methodology document v0.20. Memory-as-sole-write-target: display numbers and lineage fields are written at production time and never modified afterward — the number assigned at production is the number forever. Recognition is load-bearing: display numbers make shapes and renders citable in conversation and in the navigable path, the same way assertion display numbers made assertions citable.
Layer 2 — Reference Design v0.1.2. Render lineage parallels the assertion revision chain established in Phase 3 (wasRevisionOf). Each RenderEvent may reference its prior_render_ref, forming a linked list of revisions within a declared render type. Display numbers parallel assertion display numbers — stable, sequential, engagement-scoped, never reused.
Layer 3 — Specification (Playground Spec v0.10 and prior phase CRs). Phase 10 (render dispatch): the dispatch path gains a lineage branch. Phase 17 (assertion display numbers): the assignment-at-production pattern, the partial unique constraint, the never-reuse discipline. Phase 34 (external dispatch): rerender returns RenderEvent | ExternalProductionHandle; the existing dispatch wrapper handles both. Phase 35 (carry-forward): the _advance_render_state carry-forward block gains three new fields.
phase-35-render-content-kinds → e34985c. 1,323 tests passed, 2 skipped. Alembic head at migration 0053.CC confirms before Step 1:
uv run pytest -v reports 1,323 passed, 2 skipped. If the count differs, stop and reconcile.alembic heads shows a single head. Current migration is 0053.ShapeEvent model location identified. Confirm fields include confirmed_shape_event_ref, content, state, title. Confirm no existing display_number field.RenderEvent model location identified. Confirm fields include confirmed_shape_event_ref, render_content, content_kind, state, storage_path, reference_uri, reference_metadata, multi_file_manifest. Confirm no existing display_number, prior_render_ref, or revision_strategy field.RenderSpecialist base class location identified. Confirm produce_render signature includes kwargs: confirmed_shape_event_ref, declared_render_type_ref, triggered_by, trigger, db. Confirm no existing supports_incremental_rerender attribute or rerender method._advance_render_state function location identified. Confirm it carries forward 14 fields (Phase 35 carry-forward fix). Count the field assignments in the carry-forward block._run_specialist_with_external_dispatch_handling (or the dispatch function that calls it) location identified. This is where the orchestration branch will be added.StubRenderSpecialist location identified. Confirm it has produce_render. Confirm no existing supports_incremental_rerender flag.render_events_view query patterns identified. Confirm (engagement_id, declared_render_type_ref, state) is queryable.ShapeEventResponse and RenderEventResponse schema locations identified. Confirm no existing display_number field on either.
Archive this CR to docs/phase-crs/phase-36-cr-incremental-rerender-and-naming-v0_1.md at Step 0 before Step 1 begins, per standing archival discipline.
Assertion display numbers (Phase 17) are scoped per engagement — one flat sequence. Shape and render display numbers are scoped per (engagement_id, declared_type_ref) — one sequence per shape type per engagement, one sequence per render type per engagement. The reason: an engagement with three shape types (e.g., habitat-guide, story-pages, field-notes) would produce confusing gaps if all shapes shared a single counter. Type-scoped sequences mean habitat-guide shapes are numbered #1, #2, #3 and story-pages shapes are independently numbered #1, #2, #3.
Ad-hoc objects (null declared_shape_type_ref or null declared_render_type_ref) get their own sequence within (engagement_id, NULL).
Shape display numbers are assigned when the shape is produced (before confirmation). Shapes are identifiable from the moment they're produced — the Operator sees them in pending_confirmation state and needs to tell them apart. Waiting until confirmation would leave pending shapes without numbers. This differs from assertions, where the display number is assigned at commit (the commitment ceremony). The difference is intentional: assertion display numbers mark committed knowledge; shape display numbers mark produced artifacts that need immediate identification.
Render display numbers follow the same pattern — assigned at production.
The shape title field (auto-derived from # heading in content) continues to work as it does today. But the display number, not the title, carries identity. Two shapes from amended shapings against the same manifestation both titled "Goosey Finds the Lost Duckling" are distinguished by their display numbers: story-pages #1 vs story-pages #2.
No render title field is added. The render's identity is {render_type_name} #{display_number}. The source shape reference provides content context.
Prior position (scoping note v0.1): render title field auto-composed from shape title + render type name. Set aside in v0.2. Display numbers make the composed title unnecessary.
When a prior produced render exists for a (engagement_id, declared_render_type_ref) and the specialist does not support incremental re-render, the engine still calls produce_render (not rerender) but sets prior_render_ref to the prior render. revision_strategy is set to "fresh" to distinguish from "incremental". This means the lineage chain captures all renders for a declared render type, not just incremental ones.
Prior position (engine strategy doc §6.4 pseudocode): only set prior_render_ref in the incremental case. Set aside during scoping because lineage is more useful when always recorded. The revision_strategy field carries the distinction.
Assertion display numbers use a per-engagement counter row (engagements.next_display_number). Shape and render display numbers use MAX(display_number) + 1 within the production transaction, scoped to (engagement_id, declared_type_ref). The reason: a per-type counter row would require a new counter table or a counter row per type declaration per engagement, which is heavier machinery for a simpler problem. The MAX approach, combined with transactional isolation and the partial unique constraint, is safe. The partial unique constraint on (engagement_id, declared_type_ref, display_number) WHERE display_number IS NOT NULL prevents duplicates.
StubRenderSpecialist gains a configurable supports_incremental_rerender flag (default False) and a rerender method. No subclass. StubRenderSpecialist(supports_incremental_rerender=True) is readable and keeps the test surface in one class.
New field on ShapeEvent:
display_number: int | None = None
Sequential within (engagement_id, declared_shape_type_ref). Assigned at production time. Persisted. Never changes. Never reused. Survives confirmation, retirement, and supersession.
Assignment logic (within the shape production transaction):
from sqlalchemy import func, select
max_num = await db.scalar(
select(func.coalesce(func.max(ShapeEventsView.display_number), 0))
.where(ShapeEventsView.engagement_id == engagement_id)
.where(ShapeEventsView.declared_shape_type_ref == declared_shape_type_ref)
)
shape_data["display_number"] = max_num + 1
CC will identify the correct query target (view model or event log) during pre-flight and adjust the query accordingly. The constraint is: the next number must be one greater than the current maximum for the (engagement_id, declared_shape_type_ref) scope, and the assignment must be transactionally safe.
For ad-hoc shapes (null declared_shape_type_ref), the scope is (engagement_id, NULL). The query filters with IS NULL:
.where(ShapeEventsView.declared_shape_type_ref.is_(None))
New field on RenderEvent:
display_number: int | None = None
Sequential within (engagement_id, declared_render_type_ref). Assignment logic mirrors shapes:
max_num = await db.scalar(
select(func.coalesce(func.max(RenderEventsView.display_number), 0))
.where(RenderEventsView.engagement_id == engagement_id)
.where(RenderEventsView.declared_render_type_ref == declared_render_type_ref)
)
render_data["display_number"] = max_num + 1
Same ad-hoc handling for null declared_render_type_ref.
The full identity chain for any render:
{engagement_title} · M{manifestation_version} · {shape_type_name} #{shape_display_number} · {render_type_name} #{render_display_number}
Each segment is system-derived. The Operator named the engagement and the type declarations. Everything else is auto-assigned.
Existing shapes and renders need display numbers assigned retroactively (migration 0054 backfill, Section 7).
Shape backfill: within each (engagement_id, declared_shape_type_ref), order by created_at, assign 1, 2, 3...
Render backfill: within each (engagement_id, declared_render_type_ref), order by created_at, assign 1, 2, 3...
Two new fields on RenderEvent:
prior_render_ref: MemoryRef | None = None
revision_strategy: Literal["fresh", "incremental"] = "fresh"
No cross-field validation between these fields and the existing fields. prior_render_ref is orthogonal to content_kind.
For a given (engagement_id, declared_render_type_ref), find the most recent RenderEvent in state=produced.
declared_render_type_ref) → no prior-render lookup → always fresh.declared_render_type_ref, not on shape revision. The engine does not inspect wasRevisionOf. The specialist receives both refs and can inspect the relationship if it needs to.Four cases in the dispatch path:
produce_render. prior_render_ref = None. revision_strategy = "fresh".rerender. prior_render_ref set. revision_strategy = "incremental".produce_render. prior_render_ref set. revision_strategy = "fresh".produce_render. prior_render_ref = None. revision_strategy = "fresh". (No lookup performed.)Migration 0054 adds four columns across two models.
display_number — integer, nullable (backfilled). Partial unique constraint: UNIQUE(engagement_id, declared_shape_type_ref, display_number) WHERE display_number IS NOT NULL.display_number — integer, nullable (backfilled). Partial unique constraint: UNIQUE(engagement_id, declared_render_type_ref, display_number) WHERE display_number IS NOT NULL.prior_render_ref — JSONB, nullable.revision_strategy — VARCHAR(16), default 'fresh', CHECK constraint IN ('fresh', 'incremental').Within the migration:
Shape backfill. For each distinct (engagement_id, declared_shape_type_ref) group in the shape events storage, order by created_at, assign display numbers 1, 2, 3...
Render backfill. For each distinct (engagement_id, declared_render_type_ref) group in the render events storage, order by created_at, assign display numbers 1, 2, 3...
The Loomworks engagement has ~1 confirmed shape and ~1 produced render. The Goosey engagement has multiple shapes and renders. Backfill produces a sensible sequence for both.
The projector reads all new fields via payload.get(field, default):
payload.get('display_number', None) — on both ShapeEvent and RenderEvent projections.payload.get('prior_render_ref', None)payload.get('revision_strategy', 'fresh')Pre-Phase-36 events default cleanly. Mirrors Phase 35's approach.
The current_memory_objects materialized view and render_events_view / shape events view are refreshed to include the new columns.
class RenderSpecialist:
# Existing: produce_render(...)
# New in Phase 36:
supports_incremental_rerender: bool = False # Class-level declaration
async def rerender(
self, *,
prior_render_event_ref: MemoryRef,
confirmed_shape_event_ref: MemoryRef,
declared_render_type_ref: MemoryRef | None,
triggered_by: ActorRef,
trigger: str,
db: AsyncSession,
) -> RenderEvent | ExternalProductionHandle:
"""Called when prior render exists and specialist supports incremental.
Returns a RenderEvent with content reflecting the incremental update,
or an ExternalProductionHandle for async incremental work.
Default implementation raises NotImplementedError.
"""
raise NotImplementedError("Specialist does not support incremental rerender")
Full signature — same context kwargs as produce_render plus prior_render_event_ref. Return type includes ExternalProductionHandle for async incremental work (Phase 34 dispatch wrapper applies to both produce_render and rerender).
StubRenderSpecialist gains:
supports_incremental_rerender flag (default False), settable at construction: StubRenderSpecialist(supports_incremental_rerender=True).rerender method that returns a canned RenderEvent with revision_strategy="incremental" and prior_render_ref set from the prior_render_event_ref argument. This confirms the engine threaded the arguments correctly.No subclass. One configurable stub.
The render dispatch logic — in _run_specialist_with_external_dispatch_handling or the function that calls it — gains a prior-render-aware branch. Pseudocode:
# 1. Determine prior render
prior_render = None
if declared_render_type_ref is not None:
# Query for most recent produced render in this (engagement, render_type)
prior_render = await db.scalar(
select(RenderEventsView)
.where(RenderEventsView.engagement_id == engagement_id)
.where(RenderEventsView.declared_render_type_ref == declared_render_type_ref)
.where(RenderEventsView.state == "produced")
.order_by(RenderEventsView.version.desc())
.limit(1)
)
# 2. Assign display number (always, regardless of lineage)
display_number = await _assign_render_display_number(
db, engagement_id, declared_render_type_ref
)
# 3. Dispatch
if prior_render is not None and specialist.supports_incremental_rerender:
result = await specialist.rerender(
prior_render_event_ref=prior_render.ref,
confirmed_shape_event_ref=confirmed_shape_event_ref,
declared_render_type_ref=declared_render_type_ref,
triggered_by=triggered_by,
trigger=trigger,
db=db,
)
# Handle ExternalProductionHandle via existing dispatch wrapper
new_render_event.prior_render_ref = prior_render.ref
new_render_event.revision_strategy = "incremental"
elif prior_render is not None:
result = await specialist.produce_render(...)
new_render_event.prior_render_ref = prior_render.ref
new_render_event.revision_strategy = "fresh"
else:
result = await specialist.produce_render(...)
# prior_render_ref stays None, revision_strategy stays "fresh"
new_render_event.display_number = display_number
CC will integrate this into the actual dispatch path structure, which may differ in form from the pseudocode. The behavioral contract is: the four orchestration cases from Section 6.3 are implemented, display numbers are assigned, and the Phase 34 dispatch wrapper handles ExternalProductionHandle from both produce_render and rerender.
Shape display number assignment is placed at the point where shapes are produced (in the shaping agent or shape production path). The assignment follows the same MAX + 1 pattern (Section 5.1). CC identifies the correct insertion point during pre-flight.
_advance_render_state
The carry-forward block in _advance_render_state gains three new field assignments:
# Existing 14 fields carried forward (Phase 35 total)...
# New in Phase 36:
new_event_data["display_number"] = current_event.display_number
new_event_data["prior_render_ref"] = current_event.prior_render_ref
new_event_data["revision_strategy"] = current_event.revision_strategy
The _advance_render_state docstring total goes from 14 to 17 fields. Update the docstring to enumerate all 17 carried-forward fields.
ShapeEventResponse gains:
display_number: int | None = None
RenderEventResponse gains:
display_number: int | None = None
The prior_render_ref and revision_strategy fields are not added to the response schema in Phase 36. They are substrate-internal lineage data. A future phase (version history panel) may expose them. For now, the frontend uses display numbers for identification and the API does not surface lineage.
The shape card provenance line gains #{display_number} as the leading identifier, displayed in monospace (font-variant-numeric: tabular-nums or the design system's mono weight). Example:
> #3 "Crystal River Manatee Habitat Overview" · M2 · Produced
For shapes without a title (no # heading in content):
> #3 · M2 · Pending confirmation
For shapes without a display number (pre-backfill edge case, should not occur after migration but handled defensively):
> "Crystal River Manatee Habitat Overview" · M2 · Produced
(Falls back to current behavior — no crash.)
The render card provenance line gains #{display_number} as the leading identifier. The source shape reference uses the shape's type name and display number instead of the shaping name. Example:
> #2 · from habitat-guide #3 · Produced
Today's "From [Shaping name] · Shape v[N]" reference becomes "from {shape_type_name} #{shape_display_number}". The shaping name drops out of the render card — the shape type name and display number carry identity.
For renders without a display number (pre-backfill edge case):
> from habitat-guide #3 · Produced
(Omit render display number — no crash.)
Display numbers are shown on existing shape and render cards. No new components, no new surfaces. The provenance line updates are modifications to existing card components.
test_shape_display_number_assigned_at_production — Produce a shape. Verify display_number is 1. Produce a second shape of the same type. Verify display_number is 2. Sequential within (engagement, shape_type).
test_shape_display_number_persists_through_confirmation — Produce a shape (gets display number 1). Confirm it. Verify display_number is still 1.
test_shape_display_number_persists_through_retirement — Produce and confirm a shape (display number 1). Retire it. Verify display_number is still 1. Produce another shape of the same type. Verify it gets display number 2 (not 1 — numbers never reused).
test_shape_display_number_per_engagement_isolation — Produce a shape of type A in engagement 1 (gets #1). Produce a shape of type A in engagement 2 (gets #1). Independent sequences.
test_render_display_number_assigned_at_production — Produce a render. Verify display_number is 1. Produce a second render of the same type. Verify display_number is 2.
test_render_display_number_carries_forward_on_retire — Produce a render (display number 1). Retire it. Verify the retired render event carries display_number 1.
test_render_display_number_carries_forward_on_invalidate — Produce a render (display number 1). Invalidate it. Verify the invalidated render event carries display_number 1.
test_adhoc_shape_display_number — Produce an ad-hoc shape (null declared_shape_type_ref). Verify it gets display number 1 in the ad-hoc sequence. Produce another ad-hoc shape. Verify it gets #2.
test_adhoc_render_display_number — Produce an ad-hoc render (null declared_render_type_ref). Verify it gets display number 1 in the ad-hoc sequence.
test_backfill_produces_correct_sequences — Verify that the migration backfill assigned correct sequential numbers to existing shapes and renders, ordered by created_at within their scope.
test_incremental_rerender_dispatched_when_supported — Register a specialist with supports_incremental_rerender=True. Produce a first render (fresh). Trigger a second render. Verify the specialist's rerender method was called (not produce_render). Verify prior_render_ref is set to the first render. Verify revision_strategy is "incremental".
test_fresh_fallback_when_incremental_not_supported — Register a specialist with supports_incremental_rerender=False. Produce a first render. Trigger a second render. Verify produce_render was called. Verify prior_render_ref is set (lineage recorded). Verify revision_strategy is "fresh".
test_lineage_recorded_for_fresh_rerender — Same as above but explicitly verify that prior_render_ref is not None even though the specialist used produce_render. This is the key case 3 behavior.
test_revision_strategy_correctly_set — Verify "incremental" when rerender is called, "fresh" when produce_render is called with a prior, "fresh" when no prior exists.
test_first_render_no_prior — First render for a declared render type. Verify prior_render_ref is None. Verify revision_strategy is "fresh".
test_prior_render_lookup_excludes_retired — Produce a render, retire it. Trigger a new render. Verify no prior is found — dispatch is fresh with prior_render_ref = None.
test_prior_render_lookup_excludes_invalidated — Produce a render, invalidate it. Trigger a new render. Verify no prior is found.
test_adhoc_renders_always_fresh — Produce an ad-hoc render. Produce another. Verify no prior-render lookup is performed — both are fresh with prior_render_ref = None.
test_lineage_chain_traversable — Produce three renders for the same declared render type. Walk back through prior_render_ref from the third to the first. Verify the chain is complete.
test_prior_render_ref_carries_forward_on_retire — Produce a render with lineage (second render). Retire it. Verify the retired event carries prior_render_ref.
test_revision_strategy_carries_forward_on_retire — Same test for revision_strategy.
test_rerender_returning_external_handle — Specialist's rerender returns ExternalProductionHandle. Verify the Phase 34 dispatch wrapper handles it correctly (creates ExternalProductionRecord, enters polling loop).
test_shape_card_shows_display_number — Render a shape card with display_number: 3. Verify the provenance line contains "#3".
test_render_card_shows_display_number_and_source — Render a render card with display_number: 2 and a source shape with display_number: 3 of type "habitat-guide". Verify provenance contains "#2" and "from habitat-guide #3".
test_shape_card_without_display_number — Render a shape card with display_number: null. Verify no crash. Verify the card renders without a display number prefix.
Auto-mode posture: Steps 1–7 auto-mode-proceed. Checkpoint A halts until Operator confirms. Steps 8–9 auto. Checkpoint B (final) halts for tagging.
Archive this CR to docs/phase-crs/phase-36-cr-incremental-rerender-and-naming-v0_1.md. Run pre-flight checks (Section 3.2). Confirm baseline.
Verification: 1,323 tests passed, 2 skipped. Alembic single head at 0053. CR archived. All pre-flight items confirmed.
Commit: Phase 36 step 0: CR archival and pre-flight.
Create migration 0054. Add to ShapeEvent storage: display_number (nullable integer) with partial unique constraint on (engagement_id, declared_shape_type_ref, display_number) WHERE display_number IS NOT NULL. Add to RenderEvent storage: display_number (nullable integer) with partial unique constraint on (engagement_id, declared_render_type_ref, display_number) WHERE display_number IS NOT NULL, prior_render_ref (JSONB, nullable), revision_strategy (VARCHAR(16), default 'fresh', CHECK IN ('fresh', 'incremental')).
Backfill: within each (engagement_id, declared_shape_type_ref) group, order existing shapes by created_at, assign sequential display numbers. Same for renders within each (engagement_id, declared_render_type_ref) group.
Refresh materialized views to include new columns.
Update projector: five payload.get(field, default) calls for the new fields (Section 7.4).
Write test_backfill_produces_correct_sequences to verify the migration's backfill produces sensible numbering.
Verification: uv run pytest -v green. Migration applies and reverses cleanly.
Commit: Phase 36 step 1: migration 0054 with backfill.
Modify the shape production path to assign the next sequential display_number within (engagement_id, declared_shape_type_ref) at production time (Section 5.1). Handle ad-hoc shapes (null type ref) with IS NULL scope.
Update ShapeEventResponse schema to include display_number: int | None = None.
Write: test_shape_display_number_assigned_at_production, test_shape_display_number_persists_through_confirmation, test_shape_display_number_persists_through_retirement, test_shape_display_number_per_engagement_isolation, test_adhoc_shape_display_number.
Verification: uv run pytest -v green.
Commit: Phase 36 step 2: shape display number assignment at production.
Modify the render production path to assign the next sequential display_number within (engagement_id, declared_render_type_ref) at production time (Section 5.2). Handle ad-hoc renders with IS NULL scope.
Add prior_render_ref and revision_strategy fields to RenderEvent model (Section 6.1).
Update RenderEventResponse schema to include display_number: int | None = None.
Write: test_render_display_number_assigned_at_production, test_adhoc_render_display_number.
Verification: uv run pytest -v green.
Commit: Phase 36 step 3: render display number and lineage fields.
Add supports_incremental_rerender: bool = False class-level attribute and rerender method to RenderSpecialist base class (Section 8.1).
Extend StubRenderSpecialist with configurable supports_incremental_rerender flag and rerender method (Section 8.2).
Verification: uv run pytest -v green (no behavioral change yet — the engine doesn't call rerender).
Commit: Phase 36 step 4: specialist contract extension.
Add prior-render lookup and dispatch branch to the render dispatch path (Section 9.1). Implement all four orchestration cases (Section 6.3). The Phase 34 dispatch wrapper (_run_specialist_with_external_dispatch_handling) handles ExternalProductionHandle from both produce_render and rerender.
Write: test_incremental_rerender_dispatched_when_supported, test_fresh_fallback_when_incremental_not_supported, test_lineage_recorded_for_fresh_rerender, test_revision_strategy_correctly_set, test_first_render_no_prior, test_prior_render_lookup_excludes_retired, test_prior_render_lookup_excludes_invalidated, test_adhoc_renders_always_fresh, test_lineage_chain_traversable, test_rerender_returning_external_handle.
Verification: uv run pytest -v green.
Commit: Phase 36 step 5: engine orchestration branch.
Add three field assignments to the carry-forward block in _advance_render_state (Section 10.1). Update the docstring to enumerate all 17 fields (Section 10.2).
Write: test_render_display_number_carries_forward_on_retire, test_render_display_number_carries_forward_on_invalidate, test_prior_render_ref_carries_forward_on_retire, test_revision_strategy_carries_forward_on_retire.
Verification: uv run pytest -v green.
Commit: Phase 36 step 6: carry-forward for display number and lineage fields.
Run full substrate test suite. Verify no regressions. Verify test count is in the expected range (1,345–1,350). If any tests from prior phases fail, investigate and fix before proceeding.
Verification: uv run pytest -v green. All new tests pass. No regressions.
Commit (if fixes needed): Phase 36 step 7: substrate test sweep.
Operator confirms before frontend work begins.
All substrate work complete. Display numbers assigned at production for shapes and renders. Lineage fields populated. Specialist contract extended. Engine orchestration dispatches correctly. Carry-forward updated. All tests green.
Update shape card component: add #{display_number} to provenance line (Section 12.1). Handle null display number defensively.
Update render card component: add #{display_number} to provenance line, update source shape reference to use shape type name and display number (Section 12.2). Handle null display number defensively.
Verification: npm run lint && npx tsc --noEmit && npm run build clean. Visual inspection of shape and render cards with display numbers.
Commit: Phase 36 step 8: frontend display numbers on shape and render cards.
Write: test_shape_card_shows_display_number, test_render_card_shows_display_number_and_source, test_shape_card_without_display_number.
Verification: npm run test green. npm run lint && npx tsc --noEmit && npm run build clean.
Commit: Phase 36 step 9: frontend tests for display numbers.
Operator confirms. Tag both repos.
Tag: phase-36-incremental-rerender-and-naming on both DUNIN7/loomworks and DUNIN7/loomworks-ui.
Implementation notes: docs/phase-impl-notes/phase-36-implementation-notes-v0_1.md absorbs execution-time surprises and findings.
display_number assigned at production, sequential within (engagement_id, declared_shape_type_ref).display_number assigned at production, sequential within (engagement_id, declared_render_type_ref).#{display_number} as leading identifier.#{display_number} and source shape reference with display number.supports_incremental_rerender=True receives rerender call when prior produced render exists.supports_incremental_rerender=False receives produce_render call even when prior exists.prior_render_ref is set in both cases (incremental and fresh re-render) when a prior exists.revision_strategy correctly distinguishes "incremental" from "fresh".prior_render_ref = None and revision_strategy = "fresh".prior_render_ref.prior_render_ref, revision_strategy, and display_number carry forward on retire and invalidate.rerender returning ExternalProductionHandle is handled by the Phase 34 dispatch wrapper.phase-36-incremental-rerender-and-naming on both repos.current-status-manifest-v0_27.md (or whatever version is current) absorbs Phase 36.declared_render_type. If a render type changes, lineage does not carry across.prior_render_ref chain.prior_render_ref and revision_strategy in the API response. Substrate-internal for now. Future phase may expose for version history UI.
Read the Change Request document at the path I supply below. This is
CR-2026-049 v0.1, the Phase 36 Change Request. You are the executing
agent named in the CR.
CR path: ~/Downloads/phase-36-cr-incremental-rerender-and-naming-v0_1.md
Phase 36 adds navigable display numbers to shapes and renders
(sequential within (engagement, type), assigned at production,
never reused) and incremental re-render lineage (prior_render_ref,
revision_strategy, supports_incremental_rerender specialist
declaration, engine orchestration branch). Full-stack phase.
Code baseline: tag phase-35-render-content-kinds on both repos.
Substrate: 1,323 tests, 2 skips. Frontend: 246 vitest.
Run pre-flight (Step 0) per Section 3.2. The Step 0 checklist
includes: ShapeEvent and RenderEvent model locations; display_number
field absence confirmed; RenderSpecialist base class location;
_advance_render_state carry-forward block field count (14);
dispatch wrapper location; StubRenderSpecialist location;
render_events_view queryability; ShapeEventResponse and
RenderEventResponse schema locations; frontend shape and render
card component locations.
Per Section 3.3: archive this CR to
docs/phase-crs/phase-36-cr-incremental-rerender-and-naming-v0_1.md
at Step 0 before Step 1 begins.
Per Section 14, nine steps with two checkpoints. Auto-mode posture:
Steps 1–7 accept auto-mode-proceed; Checkpoint A halts until
Operator confirms. Steps 8–9 auto; Checkpoint B (final) halts
for tagging.
Migration 0054 (or whatever number is next available at execution).
Four columns across two models with backfill.
Pre-flight surprises (Section 3.2 ground-truth divergence) stop
execution at Step 0 and drive a CR v0.2 revision; do not proceed
through divergence.
Implementation notes at Checkpoint B:
docs/phase-impl-notes/phase-36-implementation-notes-v0_1.md
absorbs execution-time surprises and findings.
DUNIN7 — Done In Seven LLC — Miami, Florida Phase 36: Incremental Re-render and Navigable Naming Chain — CR v0.1 — 2026-05-04