Skip to content

feat(harness): add TesseraRuntime with Application, add TesseraHarness, and retire the Showcase - #64

Merged
robfeldmann merged 17 commits into
mainfrom
phase-4/03-harness-runtime
Oct 24, 2026
Merged

robfeldmann merged 17 commits into
mainfrom
phase-4/03-harness-runtime

Conversation

@robfeldmann

@robfeldmann robfeldmann commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Plan 036 Slice 3, stacked on #59 (base phase-4/02-seams, merged with its head 08174d2). Draft: Slice 4 builds on it.

What changes

  • TesseraRuntime (new target, re-exported by Tessera): Application: AnyObject with body, apply(_:), and a defaulted isFinished, per the spec's Runtime isolation section and Decision 9 (i). No @MainActor, no SendableMetatype. Its DocC carries the four adoption shapes. Tests/TesseraRuntimeTests compiles a plain class, an @Observable class, a @MainActor final class ...: @MainActor Application, and extension Counter: Application {}; the default-main-actor shape stays pinned by Slice 4's fixtures.
  • TesseraHarness (new library product beside TesseraTestSupport): the HeadlessRunner spike's driver, scenario parser, runner, report, key names, locators, registry, and two fixtures, with its 18 tests and Scenarios/*.scn (byte-identical). Every public became package, so the target declares no public name. DemoApp and DemoRegistry stand in for Application and ApplicationRegistry until Slice 4.
  • Boundaries: allowlist rows for both targets in scripts/check-package-boundaries.py (one unit test added), the shared swiftSettings, and docs:targets/docs:merge know TesseraRuntime.
  • Showcase retired: Examples/Sources/TesseraShowcase, its tests and Xcode schemes, core:showcase, the manifest entries only it used, and its mentions in README.md, CONTRIBUTING.md, docs/project-status.md, and the changelog.
  • Examples/Sources/ApplicationConsumer: a one-file target that imports only Tessera and declares an Application, so the Examples build in CI fails if Tessera stops re-exporting TesseraRuntime.
  • .gitattributes pins *.scn to LF; docs/architecture/developer-tooling.md marks Application built; both spike READMEs point at the maintained copy.

Verification

  • mise run core:test: 700 tests green on Xcode 26.6 (676 on the base, plus 6 runtime and 18 harness tests); the same 700 on Xcode 27.1.
  • mise run quality:format, quality:lint (includes quality:architecture), and docs:lint green; swift build and swift test --package-path Examples green (13 tests).
  • git diff --name-only origin/phase-4/02-seams...HEAD lists no Package.resolved.
  • CI runs 40059692549 (53b7a25) and 40060342201 (merged head e05d6e9) were green on every leg, Windows and the static Linux SDK included; the critic-fix head 2c28bb6 (run 40061964424) was green too, and the wording-fix head 096cec9 is running.

Critic review fixes (2c28bb6)

  • F1: the Application docs and the spec now state the rule by compiler mode. A bare extension Notes: Application {} on a main-actor type is a compile error in plain Swift 6 mode, builds with default main-actor isolation in the extension's module, and builds under InferIsolatedConformances (on Swift 6.3.3 only for a type declared @MainActor, not one isolated through its module's default isolation; Swift 6.4 infers both). Checked with a scratch two-module SwiftPM package on 6.3.3 and 6.4; Slice 4's second fixture runs in plain Swift 6 mode (096cec9).
  • F2: ApplicationConsumer guards the cross-package import Tessera; Slice 4's Verify line records that its driver snapshot test restores the graph-to-session-to-bytes coverage the Showcase test gave.
  • F3: the harness's unused CLI is removed; the spike keeps the original and Slice 4 writes HarnessCLI fresh.

Decisions for the owner

All recorded in the plan under Decisions and Discoveries; the reversible ones are marked "(owner to weigh)":

  • Copied, not moved. The spikes README and plan 033 A.3 say the migration is a copy, and deleting spikes/HeadlessRunner would delete its Package.resolved. The spike stays as frozen evidence.
  • No per-OS sources: shims. The moved code is Foundation only, so there was no divergent platform code to isolate; the first seam arrives with Slice 4's live runner.
  • .gitattributes earlier than Slice 5. A "\r\n" is one Swift Character, so a CRLF checkout would read a whole scenario as one line.
  • The harness depends on the narrow modules it imports, not on Tessera; TesseraRuntime is a target, not a product.

…ort slice, and record the catalog capability audit

Record the owner's 2026-10-23 decisions in plan 036: Flow and ViewThatFits get their own slice before styling, the terminal colour-report probe becomes Slice 1 and runs in parallel, catalog requirements gain Focus, Pointer, Scroll, and Clock subsections that the capability slices work through, and the coverage walk reports levels, key-and-count routes, and stop reasons. Renumber the slices, add unattended-execution rules, update the developer tooling page, and add research record 043 with the audit's placements and drafted gap requirements for owner review.
Add the package-level seams Slice 2 of plan 036 lists, adapted from the seams
spike patch to today's sources, with no new public API:

- InMemoryTerminalDevice: package init(...keepsInputOpen:), deliverResize(to:),
  and closeInput(). An open device registers its byte and size-change streams
  when terminalDevice is read, so a resize delivered afterwards is never lost
  and late readers see no replayed history.
- InMemoryTerminalSession: package init(device:) to wrap an open device.
- TerminalSession: lastDrawn (buffer, cursor, sequence; set after a successful
  flush), size, and outputTap (teed before the draw's io.write);
  withApplicationTerminal(configuration:io:...) forwards probeImageID and
  probeSleep.
- ViewGraph: a package init taking the clock the passes read.

The seam tests live in TerminalSessionTests and ViewGraphClockTests.
Make TerminalSession.draw a nonisolated(nonsending) entry point, per the
runtime isolation design in the Phase 5 spec proposal: the frame body runs in
the caller's isolation, so a @mainactor host renders a non-Sendable graph
where it lives, and the session serializes draws and commits the finished
buffer on itself. sending is dropped from the body and the result.

The draw splits into beginFrame (size and rendering policy, fixed once),
the body in the caller's isolation, and commitFrame (encode, tee outputTap,
write, flush, then set lastDrawn after a successful flush). The seam
semantics are unchanged and nothing new is public.

A @mainactor test host draws through an InMemoryTerminalDevice session; the
same code did not compile against the sending signature.
The draw body now runs in the caller's isolation, so the scope that owns the
session can close between beginFrame and the commit. commitFrame checks that
the session is open before it encodes, taps, or writes, so a draw caught in
that window throws TerminalSessionError.closed instead of painting a frame onto
the restored terminal.

Document that deliverResize(to:) also emits where the public resize(to:) only
changes the reported size, amend the Phase 5 spec's draw transaction and plan
036's Slice 2 text to match, and narrow the Delivers line to the main-actor
host the test uses.
TesseraRuntime holds the app contract: Application refines only AnyObject, with body, apply(_:), and a defaulted isFinished, and its documentation carries the four adoption shapes. Tessera re-exports it, and TesseraRuntimeTests compiles each shape against import Tessera.

TesseraHarness is a library product beside TesseraTestSupport. It carries the HeadlessRunner spike's driver, scenario parser, runner, report, and registry with its scenario corpus and tests, every public declaration now package. The spike stays under spikes/ as frozen evidence.

Both targets get boundary allowlist rows and the shared swiftSettings, docs:targets and docs:merge know TesseraRuntime, and .gitattributes pins scenario files to LF.
Each view component ships its own demo instead of one monolithic app. Delete Examples/Sources/TesseraShowcase, its tests and Xcode schemes, the core:showcase task, and the manifest entries only the Showcase used, and point README.md, CONTRIBUTING.md, and docs/project-status.md at core:example.
…ement

Check the Slice 3 box with its evidence, move Next: to Slice 4, record the copy-not-move, access, dependency, shim, and .gitattributes decisions the owner may want to weigh, add the discoveries, and update the changelog.
# Conflicts:
#	docs/plans/phase-4-view-layer-and-developer-tooling.md
…the unused CLI

Qualify the Application docs and the spec: leaving @mainactor off an extension conformance is a compile error from another module and in plain Swift 6 mode, but a module with default main-actor isolation or InferIsolatedConformances infers the isolated conformance for its own types.

Add Examples/Sources/ApplicationConsumer, a one-file target that imports only Tessera and declares an Application, so the Examples build fails if Tessera stops re-exporting TesseraRuntime. Remove the harness's CLI, which had no caller and no test; Slice 4 writes HarnessCLI fresh and the spike keeps the original.

Record in plan Slice 4 that its driver snapshot test restores the graph-to-session-to-bytes coverage the Showcase test gave, and note the three findings under Discoveries.
A scratch two-module SwiftPM package on Swift 6.3.3 and 6.4 settled when a bare extension conformance on a main-actor type compiles: it is an error in plain Swift 6 mode, builds with default main-actor isolation in the extension's module, and builds under InferIsolatedConformances (on 6.3.3 only for a type declared @mainactor, not one isolated through its module's default isolation). Rewrite the Application docs, the spec paragraph, and the plan's Discoveries and Slice 4 Verify to say so, run Slice 4's second fixture in plain Swift 6 mode, and describe ShotArtifact without the removed CLI.
Comment thread Package.swift
Comment thread Sources/TesseraHarness/Driver.swift Outdated
Comment thread Sources/TesseraHarness/Scenarios/Locator.swift Outdated
Comment thread Sources/TesseraRuntime/Application.swift Outdated
Comment thread Sources/TesseraRuntime/TesseraRuntime.docc/TesseraRuntime.md Outdated
Comment thread Tests/TesseraHarnessTests/ScenarioTests.swift
Base automatically changed from phase-4/02-seams to main October 24, 2026 15:56
…ntime

# Conflicts:
#	CHANGELOG.md
#	docs/plans/phase-4-view-layer-and-developer-tooling.md
Sort the root and Examples manifests alphabetically, state enduring facts in comments and DocC instead of plan numbers, conform Locator and FrameExpectation to CustomStringConvertible, say what Application's class-only contract means, express the scenario tests as snapshots, and record the owner's approval of the Slice 3 decisions.
@robfeldmann
robfeldmann marked this pull request as ready for review October 24, 2026 20:39
@robfeldmann
robfeldmann merged commit 8a4a95c into main Oct 24, 2026
16 checks passed
@robfeldmann
robfeldmann deleted the phase-4/03-harness-runtime branch October 24, 2026 20:40
robfeldmann added a commit that referenced this pull request Oct 24, 2026
Slice 4 deletes ApplicationConsumer and moves the harness's demos and registry; merging the squashed #64 re-added them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant