# Developing Craft++ Dream Maker

Run commands from this directory. The application targets .NET 8 and WPF.
Use Windows with the .NET 8 SDK for building and running the complete test suite.

```powershell
dotnet build codelanguage.csproj -f net8.0-windows
dotnet test CraftPlusPlus.Tests/CraftPlusPlus.Tests.csproj
```

The authoritative release entry point is `..\installer\build-release.ps1`.
It restores and builds both application targets, the Hub and updater, runs all
three test projects unless `-SkipTests` is supplied, creates the installer, and
generates a SHA-256-verified update feed under `artifacts`.

For the document workflow regressions only:

```powershell
dotnet test CraftPlusPlus.Tests/CraftPlusPlus.Tests.csproj --filter FullyQualifiedName~DocumentWorkflowTests
```

These tests create WPF controls on an STA thread without showing the main window
or triggering its online update check. They cover independent untitled documents,
Save All, background saves, failed Save As, filename collisions, tab bookkeeping,
caret restoration, window titles, and diagnostic navigation. Each test uses a
temporary directory and removes it afterward.

On Linux, cross-compile the application and tests with:

```bash
dotnet build codelanguage.csproj -p:EnableWindowsTargeting=true
dotnet build CraftPlusPlus.Tests/CraftPlusPlus.Tests.csproj -p:EnableWindowsTargeting=true
```

Cross-compilation alone does not validate WPF behavior. The document tests must
still run in a working Windows desktop runtime environment.

## Manual save/close checks

1. Create two new documents, enter different text, and save each. Confirm each
   receives its own filename and contains the correct text.
2. Edit two saved files and choose Save All. Confirm both files change on disk
   and both dirty markers clear without changing the selected tab.
3. Close a dirty background tab and choose Yes. Confirm it saves that document
   and leaves the active document intact.
4. Close a dirty untitled tab, choose Yes, and cancel Save As. Confirm the tab
   remains open with its text and dirty marker. Repeat with an unwritable path.
5. Choose Close All and cancel an unsaved-change prompt. Confirm processing
   stops and the canceled tab and subsequent tabs stay open.
6. Try the title-bar close button, Alt+F4, and File > Exit with unsaved documents.
   Confirm each offers saving and Cancel keeps the IDE open.
7. Save As to a filename already open in another tab. Confirm neither buffer nor
   destination file is overwritten and the status/output explains the conflict.
8. Switch between files with different caret positions. Confirm the caret and
   window title follow the selected file. Open an output diagnostic containing
   both a line and column and confirm navigation respects both.

## Remaining work

- Legacy brace fixtures: the brace-form sources in `CoreLanguageTests` /
  `Phase1OperatorsAndLanguageTests` compile and pass today through the
  CraftBuild path (full suite green), so they need no migration to stay green;
  `CompilerSyntaxTests` covers the indentation-only grammar enforced by the IDE
  compile path. `CraftBuildInterfaceResourceTests` uses the indentation form
  end to end as the canonical style going forward.

## Editor find/replace and undo

Find/Replace uses the non-modal `FindReplaceBar` (Ctrl+F, Ctrl+H, Enter/Shift+Enter,
F3/Shift+F3, Esc) with match case / whole word options and an all-matches
highlight in both editors (`EnhancedCodeEditor` renders them directly;
AvalonEdit surfaces highlight via `SearchMatchBackgroundRenderer`). It is
shared between `MainWindow` and `EditorWindow` through `IFindReplaceTarget`
(`AvalonEditFindReplaceTarget` adapts AvalonEdit). Replace All is a single
undo step in both editors.

Undo/Redo in `EnhancedCodeEditor` is snapshot-based (`UndoStack`) with typing
coalescing: consecutive single-character inserts or backspaces collapse into one
undo step, and every mutation path (typing, Enter, Tab block indent, cut/paste,
completion insert, find-replace) records exactly one step. `EditorSearch` and
`UndoStack` are pure logic and covered by `EditorSearchTests` / `UndoStackTests`
in the main test project (they run on any OS).

Manual find/replace checks (Windows):

1. Ctrl+F, type a query: matches highlight on the custom editor surface and the
   counter shows "N of M". Enter/Shift+Enter and F3/Shift+F3 cycle with wrap.
2. Ctrl+H: the replace row appears; Enter replaces the current match and
   advances; Replace All reports the count and one Ctrl+Z reverts it all.
3. Esc closes the bar and returns focus to the editor.
4. In the AvalonEdit editor (MainWindow/EditorWindow), the same bar works and
   all matches highlight behind the text (`SearchMatchBackgroundRenderer`).

## Interface editor → game client

The interface window editor's design is applied to the game client when the
project is built, compiled, and run:

- Saving a new interface from the editor writes the project's canonical
  `interface.cpf` (created by `IdeFileFlow.Create` when missing) instead of an
  arbitrary location, and opening any `.cpf` always uses the interface window
  editor.
- `CraftBuild` packs the whole project tree (including `interface.cpf`) into the
  `.rsc` resource bundle as `resource/…`.
- On Run (and F7 run-to-test, which reuses the last build's `.rsc`),
  `CraftSeekerWindow.ApplyRuntimeSkin` resolves the skin through
  `RuntimeSkinLocator` — extracted `.rsc` bundle first, project directory as
  fallback, `interface.*` preferred — and builds the runtime windows, controls,
  anchors, and `winset`/`winget` bindings from it.

`CraftBuildInterfaceResourceTests` and `RuntimeSkinLocatorTests` lock this
contract (they run on any OS). Manual check on Windows: design a window in
Interface Designer, save, press Run — the Seeker preview shows the designed
layout; edit the design and Run again — the preview reflects the new build.

## Interface fidelity: editor preview == Seeker runtime

Players must see the interface **exactly as authored** in the interface window
editor. That guarantee is enforced structurally, not by convention:

- `ControlVisualSpec` is the single source of truth for how a control looks:
  canonical per-kind property defaults, then authored properties, resolved into
  background, border colour/thickness, foreground, font family/size, padding and
  visibility. Both the editor preview (`InterfaceEditor.ApplyVisualSpec`) and the
  Seeker runtime (`CraftSeekerWindow.ApplyRuntimeSkin`) resolve through it.
- `DmfInterfaceDocument.Parse` stores an authored `background-color` in the
  control's `Background` field, so `ControlVisualSpec.Resolve(type, props,
  backgroundField)` accepts both sources; the runtime and the editor both pass it.
- Saving preserves an authored window background instead of forcing a fixed fill
  (`DmfInterfaceDocument.Write`), and falls back to
  `ControlVisualSpec.DefaultWindowBackground` only when none was authored.
- `InterfaceFidelityTests` asserts editor-path appearance equals runtime-path
  appearance for every control kind, with and without authored properties, and
  through a full Save → Parse → render round trip. Any future drift fails CI on
  any OS.

Manual Windows check: style controls in Interface Designer (backgrounds, text
colours, fonts, borders), save, press Run — the Seeker preview must match the
designer canvas for every control, and the window fill must match the authored
window colour rather than a forced dark/black fill.

browse()/HTML windows and the client perspective vars are wired to the game
client (Phase 6):

- `browse(Msg, Options)` builds a `/browse` descriptor; `X << browse(...)`
  delivers the HTML to X's named browser window (`browse(Who, Msg, Options)`
  delivers immediately; a bare `browse(Msg, Options);` statement delivers to
  usr). Options parse as `window=…;display=0;file=…` (`BrowseOptions`).
- The game client (CraftSeekerWindow) listens to `BrowserService.WindowUpdated`
  and renders each target as a popup `WebBrowser` window (Name = window id, so
  `winshow`/`winset` find it). `display=0` preloads the window hidden.
- `browse_rsc(File, Name)` registers client resources in
  `BrowserService.ResolveResource` (registry only; HTML `<img src>` resolution
  of registered names is not rewritten yet).
- JS binding: `byond://?key=value;…` hrefs and `window.location` calls in
  browse'd HTML are parsed by `ByondUri` and routed to `Topic(href, href_list)`
  on the player mob (`href_list["src"]` is decoded but `\ref[]` target routing
  needs the future atom-ref table).
- Client camera: `client.perspective` (`MOB_PERSPECTIVE`/`EYE_PERSPECTIVE`/
  `EDGE_PERSPECTIVE`/`PIXEL_PERSPECTIVE`) + `client.eye`/`client.mob` decide the
  render center (`ClientCamera`): MOB centers on mob; EYE/EDGE/PIXEL on
  eye (falling back to mob); EDGE/PIXEL scroll only when the camera leaves the
  current view rect (PIXEL honors pixel_x/pixel_y).
- Pixel/perspective switches that do not move the camera may wait for the next
  world tick before the view re-renders (render cache is keyed on position).

Covered by `ClientCameraTests`, `BrowseOptionsTests`, `ByondUriTests`,
`BrowserServiceTests`, and `BrowseBuiltinTests` (headless). Manual checks on
Windows: `browse("<h1>Hi</h1>", "window=map")` opens a popup; an
`<a href="byond://?action=ping">` link (or `location='byond://?action=ping'`
script) reaches `mob/Topic` with `href_list["action"]=="ping"`; setting
`client.eye` to a camera atom under `EYE_PERSPECTIVE` recenters the map.

## Indentation-only compiler checks

The language now rejects literal brace blocks and requires indented bodies.
`CompilerSyntaxTests` covers the new grammar, diagnostics, includes, source line
mapping, templates, and editor indentation. It links the real compiler front-end
sources and can run on Linux or Windows with .NET 8:

```bash
dotnet test CraftPlusPlus.Tests/CompilerValidation/CompilerValidation.csproj
```

If the environment blocks the local TCP socket required by VSTest, run the same
xUnit assertions and inline test cases in process:

```bash
dotnet run --project CraftPlusPlus.Tests/CompilerValidation/CompilerValidation.csproj
```

The older Windows test suite contains legacy brace-based language fixtures.
Those fixtures need migration to indentation before their previous success
expectations apply to the new language. The focused compiler checks above are
separate from that legacy suite; they do not claim to validate the WPF runtime.
