Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 37 additions & 117 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,117 +1,37 @@
# DarthScriptum Development Instructions

## Project structure

Organize by target, then by domain:

```text
Sources/
App/Documents/
Core/Source/
DesignSystem/
Document/{Monitoring,Persistence,Recovery}/
Document/Synchronization/{Contracts,Coordinator,Reducer}/
Editor/{Compatibility,Composition,Presentation,Rendering,Web}/
Editor/Composition/Resize/
Workspace/
Resources/
Configuration/
Tests/
Unit/{App,Core,DesignSystem,Document,Editor,Workspace,Fixtures}/
E2E/
Performance/
Architecture/
scripts/
```

- Keep runtime assets in `Resources` and build-owned files in `Configuration`.
- Keep each domain's entry surface at its root and add subfolders only for durable responsibilities shared by multiple files.
- Mirror production paths in `Tests/Unit`; use `E2E`, `Performance`, `Unit/Fixtures`, and `Architecture` only for their named purposes.
- Do not create catch-all folders or files such as `Common`, `Shared`, `Utilities`, `Helpers`, `Extensions`, `Models`, or `Misc`. Do not create a folder for one file unless it is an architectural boundary.
- Keep scoped `AGENTS.md` files only at stable domain roots. Preserve Xcode groups, target membership, build settings, scripts, and test discovery during moves.

## Architecture rules

### Dependency direction

All sources currently compile into one application target. These rules govern which directories may reference types declared in other directories; they are not Swift module-import rules.

- `Core` and `DesignSystem` are leaf domains. Product domains may depend on them, but they must not depend on `App`, `Document`, `Editor`, or `Workspace`.
- `Document` may depend on `Core` and system I/O or concurrency frameworks. It must not depend on UI domains or renderer frameworks.
- `Editor` may depend on `Core`, `DesignSystem`, bundled `Resources`, and renderer frameworks. It must not depend on concrete synchronization, persistence, or recovery implementations.
- `Workspace` may depend on `Core`, the document coordinator entry surface and its source or status contracts, editor entry surfaces, and `DesignSystem`. It may issue document commands through the coordinator but must not depend on reducer, effect-executor, persistence, recovery, or renderer implementations.
- `App` is the composition root and may reference every runtime domain to adapt native lifecycle events and wire concrete dependencies. Domain policy must remain in the owning domain.
- `Workspace/MarkdownWindowController.swift` is the sole permitted lower-layer integration with the App-owned `MarkdownDocument` host. Keep that concrete reference isolated; no other workspace or lower-domain type may reference App types.
- `Resources` contain runtime data and `Configuration` contains build-owned data; neither is a source of runtime policy.

Reverse dependencies outside the explicit window-document integration are forbidden. Introduce new cross-domain calls through an owning domain's entry surface or contract rather than by referencing its implementation details.

### Ownership

- `App` owns application and `NSDocument` lifecycle integration, native callback adaptation, and concrete dependency wiring.
- `Core` owns dependency-light cross-domain values and algorithms. `MarkdownSourceBuffer` is the sole in-memory source and revision mutator.
- `DesignSystem` owns reusable colors, typography, materials, and other visual primitives, but no feature state or policy.
- `Document` owns file authority, persistence, recovery, and synchronization policy. `DocumentSyncReducer` is the sole synchronization transition authority.
- `Editor` owns editing composition, `EditorPaneModel` presentation state, native presentation, rendering, compatibility adaptation, and local WebKit lifecycle.
- `Workspace` owns window, pane identity and composition, tab, split, focus, shortcut, and workspace-restoration composition.

Only the owner may mutate owned state. Non-owners must request changes through the owner's public contract and must not retain independently mutable copies of that state. Coordination does not transfer ownership.

### State lifetimes

- Durable document bytes, file identity, commit evidence, and recovery records are owned by `Document` persistence and recovery components and may survive process restart.
- The live source and revision are owned by one `MarkdownSourceBuffer` for the document lifetime and are shared by every editor pane.
- Synchronization workflow state, epochs, and effect tokens are owned by the document reducer and coordinator for one document lifetime; stale or mismatched completions cannot mutate current state.
- Workspace state owns live window, pane composition, split, and focus. `EditorPaneModel` owns pane-local selection, scroll, position, and renderer association; a versioned workspace restoration snapshot may capture and recreate that presentation but is never source or synchronization authority.
- Editor view, observation, rendering-cache, and WebKit-session state is disposable and must be rebuilt from owned source, pane, and workspace composition state when recreated.

### Cross-domain invariants

- Split panes share one source buffer; layout, focus, restoration, and tab changes must not create another source authority or alter document lifecycle state.
- Only `MarkdownSourceBuffer` mutates source revisions, and only `DocumentSyncReducer` selects synchronization transitions.
- The coordinator applies events serially. Effect requests are immutable and complete, completions echo their full tokens, and stale tokens are rejected.
- Blocking file and recovery work crosses `DocumentFileAccess`; it never blocks the main actor or a Swift cooperative executor.
- Durable evidence is committed before corresponding in-memory success is published. Unproven attachment, commit, recovery, ownership, or cleanup safety fails closed.
- AppKit close and quit callbacks preserve refusal and cancellation and complete exactly once.
- Renderer inputs and document-derived resource requests remain untrusted and cannot widen file, navigation, window, network, or persistence authority.

## Naming

- Follow Swift API Design Guidelines. Use `UpperCamelCase` for types, protocols, Swift files, and directories; use `lowerCamelCase` for members and values; preserve established initialisms.
- Name files after their primary declaration and keep one primary top-level type per file. Use `Type+Capability.swift` for focused extensions; avoid generic or catch-all names.
- Choose precise roles: nouns for types and values, verbs for side effects, `make` for factories, assertion-style Booleans, `Error` errors, and `-able` capability protocols. Prefer role suffixes such as `Store`, `Coordinator`, or `Renderer` over vague names such as `Manager` or `Helper`.
- Use the narrowest truthful access level; preserve required and upstream names. Name repository shell scripts with lower-kebab-case verb phrases.

## Working notes

- Do not create or commit `docs/` content; it becomes stale quickly. Put temporary documentation, plans, and handoff notes in Git-ignored `.context/`.

## Tests

- TDD is mandatory.
- Name XCTest types and files `<Subject>Tests`; name methods `test<Action>When<Condition><Outcome>()`.
- Name support types by role: `Recorder`, `Spy`, `Stub`, `Fake`, `Harness`, or `Fixture`. Use `Mock` only when it verifies interactions.
- Name shared support files `<Subject>TestSupport.swift` or `<Purpose>TestHarness.swift`. Use lower-kebab-case for repository-owned non-Swift fixtures.

## Verification

- Restrict every macOS build and test to `arm64` and use the narrowest meaningful check.

### Routine development

- Run `./scripts/lint.sh` after Swift changes; it is a fast, non-compiling check.
- Prefer focused tests: `./scripts/test.sh <test-identifier> [...]`. Use `--unit` or `--e2e` for a complete suite, and `--all` explicitly for both non-performance suites.
- Before handing back production-code changes, run relevant tests and one Debug build with `./scripts/build-debug.sh`; skip the separate build when the test command already built every affected target.
- Use Xcode **Build** (`Command-B`) for interactive compilation. Routine scripts reuse repository-local `DerivedData/`.
- Run `./scripts/check-architecture.sh` after ownership, cross-domain dependency, or scoped-instruction changes.
- Documentation and instruction changes require no build unless they alter executable scripts, build commands, or project configuration.

### Runtime and UI

- Use Xcode **Run** (`Command-R`) only when behavior must be observed in the running app. A successful launch does not replace automated tests.

### Full verification

- Run `./scripts/verify.sh` only for broad or cross-domain changes; architecture, dependency, project, scheme, Release, or performance changes; release preparation; CI-equivalent validation; or an explicit user request.
- A normal commit does not require full verification. Packaging, installation, signing, notarization, and distribution require an explicit request.
- Report the checks run and any relevant checks intentionally skipped.
# DarthScriptum development

## Boundaries and ownership

All production Swift sources compile into one application target. These directory-level contracts are not enforced by compilation. Local constraints live in each domain's `AGENTS.md`.

| Scope | Authority | Allowed dependencies |
| --- | --- | --- |
| `Sources/Core` | Live source, revisions, source algorithms | Dependency-light system frameworks such as Foundation, Combine, CryptoKit; no UI, renderer, or product domains |
| `Sources/DesignSystem` | Feature-independent visual primitives | Platform presentation frameworks; no other source domains or renderer frameworks |
| `Sources/Document` | File authority, persistence, recovery, synchronization | Core and system I/O/concurrency frameworks; no UI or renderer frameworks |
| `Sources/Editor` | Pane presentation, editing composition, rendering, compatibility, WebKit sessions | Core, DesignSystem, Resources, renderer frameworks; no concrete document synchronization, persistence, or recovery |
| `Sources/Workspace` | Windows, tabs, splits, focus, shortcuts, restoration | Core, document coordinator and source/status contracts, Editor entry surfaces, DesignSystem |
| `Sources/App` | Native application/document lifecycle and dependency wiring | All runtime domains; feature policy stays with its owner |

- `MarkdownSourceBuffer` is the sole live source/revision authority for a document, shared by every pane. `DocumentSyncReducer` is the sole synchronization transition authority. Request mutations through these owners; presentation and restoration must not become additional authorities.
- `EditorPaneModel` owns pane selection, viewport, position, and renderer association. Views, observations, caches, and WebKit sessions are disposable; recreate them from owned source and presentation state.
- Workspace must not reference reducer, effect-executor, persistence, or recovery implementations. Two integration exceptions are scoped: `MarkdownWindowController.swift` may reference App's `MarkdownDocument`; `WorkspaceModel.swift` may construct and share Editor rendering services for its panes. Neither exception transfers domain policy to Workspace.
- Renderer inputs and document-derived resource requests are untrusted and must not widen file, navigation, window, network, or persistence authority.

## Placement and project integration

- Organize sources by domain and durable responsibility. Keep runtime assets in `Sources/Resources` and build-owned files in `Sources/Configuration`. Avoid catch-all directories (`Common`, `Shared`, `Utilities`, `Models`) and one-file directories without an architectural boundary.
- Mirror production responsibilities under `Tests/Unit`; cross-domain behavior belongs in `Tests/E2E`, performance tests in `Tests/Performance`, and shell architecture fixtures in `Tests/Architecture`.
- Xcode uses filesystem-synchronized groups: new Swift files under `Sources` or a test suite automatically join that target. Do not add redundant file/build-phase entries. Non-code files under `Sources` may need membership exclusions in `DarthScriptum.xcodeproj/project.pbxproj`.
- Keep scoped guides at domain roots and `Tests`. Adding/moving/removing guides requires reconciling the allowlist in `scripts/check-architecture.sh`, its fixtures, and Xcode membership exclusions. Each guide needs a sibling `CLAUDE.md` containing only `@AGENTS.md` and a terminal newline.
- Do not create or commit `docs/` content. Keep temporary plans and handoffs in gitignored `.context/`.

## Development and verification

- TDD is required for behavior changes: establish a failing regression or feature test before changing production behavior. Instruction-only edits use the instruction checks below, not new XCTest coverage.
- Restrict macOS builds/tests to `arm64`. Repository scripts apply `Sources/Configuration/Verification.xcconfig`, disable signing, and honor the checked-in SwiftPM resolution. Do not refresh dependency pins merely to resolve a local build failure.
- After Swift changes, run `./scripts/lint.sh` and relevant tests. Focused selectors require the target: `./scripts/test.sh DarthScriptumUnitTests/MarkdownSourceBufferTests` (optionally append a method). `--unit` and `--e2e` run their suites; `--all` runs both, excluding performance.
- Run `./scripts/build-debug.sh` before handing back production changes unless tests already built every affected target. Routine builds/tests reuse `DerivedData/`. Use Xcode Build for interactive compilation and Run when runtime observation is needed.
- For dependency/ownership or scoped-instruction changes, run `./scripts/check-architecture.sh` and `Tests/Architecture/run-tests.sh`. Instruction-only edits need no macOS build unless they alter scripts, build commands, or project configuration.
- Use `./scripts/verify.sh` for broad code changes, architecture/dependency/project/scheme changes, Release/performance changes, release preparation, or explicit full validation. It includes Debug/Release builds and all three test suites. Editorial instruction changes use the checks above; a normal commit alone does not require full verification.
- Packaging, installation, signing, notarization, and distribution require an explicit request. Report checks run and relevant checks skipped.
24 changes: 4 additions & 20 deletions Sources/App/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
# App boundary
# Native document lifecycle

## Ownership

- Own application lifecycle, menus, `NSDocument` host integration, window construction, AppKit callback adaptation, and concrete dependency wiring.
- Treat `MarkdownDocument` as a native lifecycle adapter around document-owned source, persistence, recovery, and synchronization contracts; it is not the synchronization policy owner.

## Dependencies and boundary

- As the composition root, App may reference every runtime domain to construct and connect concrete dependencies.
- Translate native events into document or workspace contract calls. Do not move synchronization, persistence, workspace, editor, or renderer policy into this layer.
- Lower domains must not reference App types except for the isolated `MarkdownWindowController` to `MarkdownDocument` integration permitted by the root architecture rules.

## State and invariants

- Retain native close and quit callbacks until the owning document lifecycle reaches a decision.
- Translate callbacks to full reducer tokens, preserve every refusal or cancellation, reject stale completions, and complete each callback exactly once.

## Verification

- Verify app behavior in `Tests/Unit/App/` and close or quit behavior in `Tests/E2E/`.
- `NSDocumentController` may construct/read `MarkdownDocument` on its opening queue. Preserve the inherited Objective-C initializer and staged `DocumentInitialContentStore` handoff; a main-actor initializer or coordinator access from `read(from:ofType:)` breaks concurrent opening. Install live state on the main actor. Cover changes with `CrossBoundaryRegressionTests`.
- Keep native autosave-in-place, drafts, and versions disabled while Document owns synchronization/recovery. Managed in-place writes require a matching `SaveTransactionBridge` request and run off-main through the document's file-access lane; preserve `unblockUserInteraction()` before the blocking commit.
- Retain close/quit callbacks until Document decides; preserve refusal/cancellation, reject stale tokens, and complete callbacks exactly once. A positive `canClose` answer can reenter `close()` immediately: retain the authorized token before forwarding the answer and publish commitment only after native close. Cover changes with `DocumentCloseCharacterizationTests`.
25 changes: 5 additions & 20 deletions Sources/Core/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,6 @@
# Core boundary
# Source contracts

## Ownership

- Own canonical source values, revisions, edits, formats, identities, fingerprints, history, line indexing, and `MarkdownSourceBuffer`.
- Keep `MarkdownSourceBuffer` as the sole in-memory source and revision authority for a document.

## Dependencies and boundary

- Depend only on dependency-light system frameworks such as Foundation, Combine, and CryptoKit.
- Do not import UI, WebKit, or MarkdownEngine frameworks, perform file I/O, or reference App, Workspace, Document-host, or Editor types.
- Expose values and algorithms without acquiring lifecycle, persistence, presentation, or feature policy.

## State and invariants

- All source mutations pass through `MarkdownSourceBuffer`; non-owners may observe it or request edits but must not maintain another mutable source revision.
- Preserve expected-revision validation, monotonic revision history, change-origin semantics, and deterministic source transformations.

## Verification

- Verify Core changes in the matching `Tests/Unit/Core/` files and run the architecture guard.
- Keep source operations free of file I/O and document lifecycle or presentation policy.
- Edit/selection offsets use UTF-16 (`NSRange`/`NSString`), not Swift character counts. Reject stale expected revisions and ranges splitting surrogate pairs; cover non-BMP and combining-character cases when changing range transforms.
- Undo/redo share one history across panes. Preserve change origins and external-replacement history invalidation so synchronization and native dirty-state adaptation distinguish local edits, undo/redo, and reloads.
- Prepared metrics/indexes must describe the exact revision being installed. Preserve stale-result rejection and deferred indexing for large documents instead of adding unconditional whole-document scans on the main actor.
23 changes: 3 additions & 20 deletions Sources/DesignSystem/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,4 @@
# DesignSystem boundary
# Adaptive presentation

## Ownership

- Own reusable application colors, typography, materials, spacing, and other visual primitives.
- Keep primitives feature-independent and suitable for use by App, Editor, and Workspace presentation.

## Dependencies and boundary

- Depend only on platform presentation frameworks such as AppKit and SwiftUI.
- Do not reference App, Core, Document, Editor, or Workspace types; import renderer frameworks; perform file or network I/O; or acquire feature policy.
- Consumers choose when and why to apply a primitive. DesignSystem defines its visual meaning but does not coordinate feature behavior.

## State and invariants

- Keep primitives stateless or limited to view-local presentation state.
- Do not retain document content, synchronization state, editor lifecycle state, or workspace navigation state.

## Verification

- Verify visual primitive behavior in `Tests/Unit/DesignSystem/` and run the architecture guard after boundary changes.
- Resolve appearance-sensitive layer colors under the view's effective appearance and refresh on appearance changes; cached `CGColor` values lose `NSColor`'s dynamic behavior.
- Materials must respond to window activation and Reduce Transparency changes. Preserve an opaque, appearance-correct fallback and release window observers when moving between windows. Verify these transitions in `AppThemeTests` when changing materials or theme colors.
Loading