DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/phase-36-incremental-rerender-and-naming/phase-36-cr-incremental-rerender-and-naming-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-049 — Phase 36: Incremental Re-render and Navigable Naming Chain (v0.1)

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.


Contents


1. Executive summary

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).


2. Grounding

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.


3. Prerequisites

3.1 Baseline

3.2 Pre-flight checks

CC confirms before Step 1:

  1. uv run pytest -v reports 1,323 passed, 2 skipped. If the count differs, stop and reconcile.
  2. alembic heads shows a single head. Current migration is 0053.
  3. ShapeEvent model location identified. Confirm fields include confirmed_shape_event_ref, content, state, title. Confirm no existing display_number field.
  4. 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.
  5. 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.
  6. _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.
  7. _run_specialist_with_external_dispatch_handling (or the dispatch function that calls it) location identified. This is where the orchestration branch will be added.
  8. StubRenderSpecialist location identified. Confirm it has produce_render. Confirm no existing supports_incremental_rerender flag.
  9. render_events_view query patterns identified. Confirm (engagement_id, declared_render_type_ref, state) is queryable.
  10. ShapeEventResponse and RenderEventResponse schema locations identified. Confirm no existing display_number field on either.
  11. Frontend: shape card component location identified. Confirm provenance line structure.
  12. Frontend: render card component location identified. Confirm provenance line structure and source-shape reference.

3.3 CR archival

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.


4. Construction decisions this CR closes

D1 — Display numbers are per-type within engagement, not per-engagement flat

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).

D2 — Assignment at production, not confirmation

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.

D3 — Display numbers carry identity; content-derived titles are enrichment

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.

D4 — Lineage recorded even for fresh re-renders

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.

D5 — MAX-based assignment, not counter row

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.

D6 — Test specialist uses configurable flag, not subclass

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.


5. Feature A — Navigable naming chain

5.1 Shape display number

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))

5.2 Render display number

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.

5.3 The navigable path

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.

5.4 Backfill

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...


6. Feature B — Incremental re-render lineage

6.1 RenderEvent extension — lineage fields

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.

6.2 Prior-render lookup rule

For a given (engagement_id, declared_render_type_ref), find the most recent RenderEvent in state=produced.

6.3 Orchestration cases

Four cases in the dispatch path:

  1. No prior render → call produce_render. prior_render_ref = None. revision_strategy = "fresh".
  2. Prior exists, specialist supports incremental → call rerender. prior_render_ref set. revision_strategy = "incremental".
  3. Prior exists, specialist does NOT support incremental → call produce_render. prior_render_ref set. revision_strategy = "fresh".
  4. Ad-hoc render → call produce_render. prior_render_ref = None. revision_strategy = "fresh". (No lookup performed.)

7. Migration 0054

Migration 0054 adds four columns across two models.

7.1 ShapeEvent storage

7.2 RenderEvent storage

7.3 Backfill

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.

7.4 Projector extension

The projector reads all new fields via payload.get(field, default):

Pre-Phase-36 events default cleanly. Mirrors Phase 35's approach.

7.5 View refresh

The current_memory_objects materialized view and render_events_view / shape events view are refreshed to include the new columns.


8. Specialist contract extension

8.1 New attribute and method on RenderSpecialist


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).

8.2 StubRenderSpecialist extension

StubRenderSpecialist gains:

No subclass. One configurable stub.


9. Engine orchestration

9.1 Orchestration branch

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.

9.2 Shape display number assignment

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.


10. Carry-forward discipline

10.1 Three new fields in _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

10.2 Docstring update

The _advance_render_state docstring total goes from 14 to 17 fields. Update the docstring to enumerate all 17 carried-forward fields.


11. Response schema additions

11.1 ShapeEventResponse

ShapeEventResponse gains:


display_number: int | None = None

11.2 RenderEventResponse

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.


12. Frontend changes

12.1 Shape card provenance line

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.)

12.2 Render card provenance line

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.)

12.3 No new components or surfaces

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.


13. Test suite

13.1 Naming chain tests (substrate)

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.

13.2 Lineage tests (substrate)

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).

13.3 Frontend tests

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.

13.4 Test count projection


14. Order of operations (steps with checkpoints)

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.

Step 0 — Pre-flight and CR archival

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.

Step 1 — Migration 0054: four columns with backfill

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.

Step 2 — Shape display number assignment at production

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.

Step 3 — Render display number assignment and lineage fields

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.

Step 4 — Specialist contract extension

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.

Step 5 — Engine orchestration branch

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.

Step 6 — Carry-forward

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.

Step 7 — Substrate test sweep

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.

Checkpoint A — Substrate verification

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.

Step 8 — Frontend display numbers

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.

Step 9 — Frontend tests

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.

Checkpoint B — Final

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.


15. Acceptance gate

Naming chain

  1. A produced shape has a display_number assigned at production, sequential within (engagement_id, declared_shape_type_ref).
  2. A produced render has a display_number assigned at production, sequential within (engagement_id, declared_render_type_ref).
  3. Display numbers persist through state transitions (confirmation, retirement, invalidation).
  4. Display numbers are never reused within their scope.
  5. Ad-hoc shapes and renders (null type ref) have their own independent sequences.
  6. Existing shapes and renders have backfilled display numbers in correct order.
  7. Shape card provenance line shows #{display_number} as leading identifier.
  8. Render card provenance line shows #{display_number} and source shape reference with display number.

Lineage

  1. Specialist with supports_incremental_rerender=True receives rerender call when prior produced render exists.
  2. Specialist with supports_incremental_rerender=False receives produce_render call even when prior exists.
  3. prior_render_ref is set in both cases (incremental and fresh re-render) when a prior exists.
  4. revision_strategy correctly distinguishes "incremental" from "fresh".
  5. First render for a type has prior_render_ref = None and revision_strategy = "fresh".
  6. Prior-render lookup excludes retired and invalidated renders.
  7. Ad-hoc renders always produce fresh with no prior-render lookup.
  8. Lineage chain is traversable by walking prior_render_ref.
  9. prior_render_ref, revision_strategy, and display_number carry forward on retire and invalidate.
  10. rerender returning ExternalProductionHandle is handled by the Phase 34 dispatch wrapper.

16. Post-CR state


17. What this CR does not specify


18. Kickoff prompt for the Claude Code session


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