NOASSERTIONupdated 1mo ago
Let be the absolute plugin directory two levels above this SKILL.md. Resolve that path before running commands or opening shared references. When these instructions say swiftui-ctx, invoke /scripts/swiftui-ctx; do not assume the command is on PATH.
What can you do with Audit Swiftui Document Model?
name: audit-swiftui-document-model description: Audit macOS SwiftUI document model for correctness, current APIs, and production conventions. Use for that domain or as part of a full audit.
Bundled resource root
Let <swiftui-plugin-root> be the absolute plugin directory two levels above this SKILL.md. Resolve that path before running commands or opening shared references. When these instructions say swiftui-ctx, invoke <swiftui-plugin-root>/scripts/swiftui-ctx; do not assume the command is on PATH.
Audit SwiftUI Document Model
AUDIT-ONLY Β· macOS-only Β· SwiftUI-only. Run this on a finished or in-progress macOS SwiftUI
project to detect β and where certain, fix β every way the document architecture goes wrong:
the wrong document protocol (value FileDocument vs reference ReferenceFileDocument), a missing
snapshot(contentType:), mis-declared content types / UTTypes, mishandled FileWrapper (data loss),
mutation that bypasses the document binding (no dirty-state / no autosave), serialization pinned to the
main actor, a phantom @FocusedDocument, NSDocument smuggled into a SwiftUI app, and
DocumentGroupLaunchScene used where it has no macOS arm. Findings are written to disk in the
toolkit's unified schema; this domain has no mechanical auto-fix (every correction is judgment-
gated), so findings are emitted flag-only with the β
shown. Never a from-scratch document-app generator.
The document API surface (DocumentGroup/FileDocument/ReferenceFileDocument/FileDocumentConfiguration)
is macOS 11.0+ but thinly used, so AI frequently confuses the value vs reference split and invents
@FocusedDocument. Be suspicious wherever AI wrote document-app scaffolding.
Boundary / seam note (stay in lane)
- SwiftData (
@Model/ModelContainer/@Query) persistence is out of scope βaudit-swiftui-swiftdata. This skill owns the document file format (FileDocument/ReferenceFileDocument), not the database. - Sandbox file consent β
fileImporter/fileExporter/NSOpenPanelsecurity-scoped bookmarks belongs toaudit-swiftui-sandbox-files. This skill owns the document type and its read/write; manual file IO inside a document app (doc-11) is flagged here andcross_ref'd there. - Scene plumbing β window sizing,
MenuBarExtra, restoration belongs toaudit-swiftui-scenes-windows. TheDocumentGroupLaunchSceneno-macOS-arm trap (doc-05) is flagged here andcross_ref'd there. - Serialization Sendable / actor-isolation correctness is owned by
audit-swiftui-concurrency-safety; this skill flags the MainActor-pinned document smell (doc-08) andcross_refs it there. - Whether to bridge to AppKit at all is
audit-swiftui-appkit-overuse;NSDocumentin a SwiftUI app (doc-04) is flagged here andcross_ref'd there.
The document-model design rules (non-negotiable)
- Value vs reference is a type decision, not a style. A small, snapshot-serializable model β
struct: FileDocument(SwiftUI copies a value to write). A large/graph/incrementally-mutated model βfinal class: ReferenceFileDocument(reference semantics +snapshot(contentType:)). A class conforming toFileDocument(doc-02) defeats the value contract; aReferenceFileDocumentwithoutsnapshot(contentType:)(doc-03) does not compile / loses the write. - All edits flow through the document binding. Mutate via the
$documentbinding fromFileDocumentConfiguration(or the@ObservableObject/@Observablereference document) so SwiftUI marks the scene dirty and autosaves. Mutating a copy /configuration.documentdirectly (doc-10) means no dirty-state and silent data loss. - Declare every content type you read or write.
readableContentTypes/writableContentTypesmust be realUTTypes, and any custom UTI must be exported/imported inInfo.plist(doc-06); an editable document that omitswritableContentTypesis read-only by accident (doc-07). - Don't serialize on the main actor. Apple: "Don't perform serialization on MainActor." A document
type annotated
@MainActor(doc-08) dragsinit(configuration:)/fileWrapper(...)onto the main thread and blocks the UI on every save.
Full reasoning + the value/reference decision table: references/file-document-model.md.
Defect index (doc-01 β¦ doc-12)
id Β· tell Β· severity Β· fix Β· open reference. Severities: hard-fail (build break / never-correct),
warning (compiles but unsound), advisory (judgment / perf). All findings are flag (flag-only)
β document architecture has no mechanical single-answer fix; show the β
, the dev applies it.
| id | One-line tell | Sev | Fix | Reference |
|---|---|---|---|---|
| doc-01 | @FocusedDocument β phantom property wrapper (does not exist) |
hard-fail | flag | document-api-surface.md |
| doc-02 | a class conforming to value-type FileDocument (use ReferenceFileDocument) |
warning | flag | file-document-model.md |
| doc-03 | : ReferenceFileDocument with no func snapshot(contentType:) |
hard-fail | flag | file-document-model.md |
| doc-04 | NSDocument / NSDocumentController inside a SwiftUI app |
warning | flag | document-scene.md |
| doc-05 | DocumentGroupLaunchScene β has no macOS arm |
hard-fail | flag | document-scene.md |
| doc-06 | UTType(exportedAs: / importedAs: not declared in Info.plist |
warning | flag | content-types-utis.md |
| doc-07 | readableContentTypes set, writableContentTypes omitted on an editable doc |
warning | flag | content-types-utis.md |
| doc-08 | @MainActor on the document type β serialization on the main actor |
warning | flag | file-document-model.md |
| doc-09 | FileWrapper.regularFileContents force-unwrapped / un-guarded β data loss |
warning | flag | file-document-model.md |
| doc-10 | configuration.document mutated directly (not via the $document binding) |
warning | flag | file-document-model.md |
| doc-11 | FileManager / Data(contentsOf:) / NSSavePanel manual IO in a document app |
advisory | flag | document-scene.md |
| doc-12 | ReferenceFileDocument app with no @Environment(\.undoManager) undo wiring |
advisory | flag | file-document-model.md |
doc-10 has no clean lint tell (mutation-through-binding is semantic) β configuration.document is a
locator hint only; the real call is READ-by-hand at step 3. doc-03/doc-12 share the ReferenceFileDocument
grep locator and split in DETECT.
The real API, at a glance
Real (exist on macOS): DocumentGroup(newDocument:) / (viewing:) / (editing:migrationPlan:) (macOS 14.0+),
FileDocument (a struct protocol), ReferenceFileDocument (a class + Sendable protocol, requires
snapshot(contentType:) + fileWrapper(snapshot:configuration:)), FileDocumentConfiguration (gives the
$document binding + fileURL + isEditable), ReferenceFileDocumentConfiguration,
static readableContentTypes / writableContentTypes: [UTType], init(configuration:),
fileWrapper(configuration:) / fileWrapper(snapshot:configuration:), FileWrapper, UTType(exportedAs:) / (importedAs:),
@Environment(\.undoManager). DocumentGroupLaunchScene is iOS/iPadOS-only β NO macOS arm.
Hallucinated / wrong: @FocusedDocument (not a real symbol β custom FocusedValues @Entry key +
@FocusedValue(\.focusedDocument)); a class β¦ : FileDocument (value protocol on a reference type);
NSDocument in a SwiftUI lifecycle.
Allow-list, signatures, and the @FocusedDocument β custom-key rewrite: references/document-api-surface.md.
Floor values are the reconciled truth in <swiftui-plugin-root>/references/_shared/floors-master.md; the
canonical invented-name list (incl. @FocusedDocument) is
<swiftui-plugin-root>/references/_shared/hallucination-blacklist.md β read, never restate them.
Grounded β β the canonical document-app shape (real code, not a placeholder)
The ## Correct block on every finding is the swiftui-ctx consensus shape backed by a real macOS
example β never a hand-invented snippet. From swiftui-ctx lookup DocumentGroup --json (introduced macOS
11.0; corpus repo_count: 33): consensus (newDocument) 77%, (viewing) 8%, (editing,migrationPlan)
5%, (newDocument,editor) 3%; co_occurs_with = focusedSceneValue/focusedValue/FocusedValue/
commandsRemoved/inspector (the real focused-document wiring β not @FocusedDocument). The recommended
permalinked example (ex_91cff38b97, RobertoMachorro/Moped, min macOS 12) fetched via
swiftui-ctx file ex_91cff38b97 --smart:
// β
canonical DocumentGroup β Moped/MopedApp.swift L27-44 (verbatim from the practice corpus)
var body: some Scene {
DocumentGroup(
newDocument: { MopedDocument() },
editor: { file in
EditorView(document: file.document) // edits flow through the configuration (doc-10)
.onChange(of: file.fileURL, initial: true) { _, newURL in
file.document.fileURL = newURL
}
}
)
.commands { MopedCommands() }
Settings { PreferencesView(preferences: Preferences.userShared) }
}
- Source (canonical example):
https://github.com/RobertoMachorro/Moped/blob/5b109e33c83d38456a787115ec49fc28ced2bebe/Moped/MopedApp.swift#L28 - Spec (
doc:):https://sosumi.ai/documentation/swiftui/documentgroup
Step 7 (FIX) regenerates this same trio (consensus shape + --smart permalink + Sosumi doc:) for the
specific finding's API; the snippet above is the worked instance for DocumentGroup.
The 8-step audit workflow (execute verbatim)
- ORIENT.
tree/findthe SwiftUI sources. Confirm this is a document app (aDocumentGroupscene in theAppbody) and read the deployment target (project.pbxprojMACOSX_DEPLOYMENT_TARGET/Package.swiftplatforms:). Note whether the model is value- or reference-shaped β it drives the doc-02/doc-03 split. - LOCATE. Run the shared hybrid lint runner:
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-document-model --dir <sources> --json /tmp/doc.json --sarif /tmp/doc.sarif. It runs this skill's tier-1 grep tells (lint/grep-tells.tsv) + tier-2 structural ast-grep rules (lint/ast-grep/*.ymlβ the missing-snapshotand MainActor-pinned-document rules grep can't express), plus a per-file parse probe, and emits unified JSON + SARIF. Read itsparse_warningsβ a flagged file did not fully parse, so a structural miss can't masquerade as clean; READ those by hand. The runner only LOCATES β never treat a hit as a finding. Engine + rule-file format + degradation:<swiftui-plugin-root>/references/_shared/lint-architecture.md. - READ. Open every located file in full β never pattern-match-and-patch blind. The
value/reference choice, the binding flow, container conformance, and
Info.plistUTI declarations are invisible to grep. Build a per-file inventory: each document type + its kind (value/reference) + its content types + how edits reach it (binding vs copy) + its serialization actor. - DETECT. Apply the index. Assign each candidate a confidence; report a finding only at 100%
certainty (e.g. a
@FocusedDocument, aclass β¦ : FileDocument, aReferenceFileDocumentwith nosnapshot, aDocumentGroupLaunchSceneon a Mac target). - VERIFY. For anything β€ ~70% confidence (a symbol you're unsure exists, a floor you can't place, a
serialization-actor claim), run both evidence sources. (a) Practice β
bash <swiftui-plugin-root>/scripts/swiftui-ctx lookup <api> --json(andswiftui-ctx deprecated <api>for a currency rule): read itsconsensus(the canonical shape),deprecated+replacement,recommendedpermalink,introduced_macos, andco_occurs_with; alookupreturning not_found (ok:false,error.class: not_found+ a did-you-meansuggestion) corroborates a hallucination finding β no shipping Mac app uses the symbol (this is how@FocusedDocumentresolves). (b) Spec β confirm via Sosumi:curl -sSL https://sosumi.ai/<apple-path>usingreferences/source-directory.mdfor the path and<swiftui-plugin-root>/references/_shared/sosumi-reference.mdfor the protocol (neverWebFetchdeveloper.apple.com). Cross-checkintroduced_macosagainstfloors-master.mdand the Sosumidoc:floor. The CLI contract is<swiftui-plugin-root>/references/_shared/swiftui-ctx-reference.md. Promote with the citation or discard. - REPORT. Write each confirmed finding (output contract below). One finding per file, zero-padded,
ordered. Emit a
cross_refon shared-seam findings (doc-04 β appkit-overuse, doc-05 β scenes-windows, doc-08 β concurrency-safety, doc-11 β sandbox-files). Write the run's_index.md. - FIX. This domain is
fix_mode: flag-onlyend-to-end β no auto-fix (document type / binding / UTI changes are never a mechanical single answer). Under the fix-safety protocol (<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md) leave every findingopenwith the β in## Correct. The β is not a hand-written snippet β it is the swiftui-ctx consensus shape put in## Correct, backed by a real macOS example fetched withbash <swiftui-plugin-root>/scripts/swiftui-ctx file <recommended.id> --smartwhose GitHub permalink (plus the Sosumidoc:) goes in## Sourceas the canonical example. The canonicalDocumentGroupshape is(newDocument:editor:)(perswiftui-ctx lookup DocumentGroup,recommended=ex_91cff38b97βRobertoMachorro/MopedMopedApp.swift#L28). - DOUBLE-CHECK. Re-grep each touched file to confirm the tell no longer matches; record the evidence
in
## Fix applied?. Re-confirm every citation still resolves and still says the floor it claimed. If a change introduced a new tell (e.g. switching toReferenceFileDocumentnow needs asnapshot), loop that file back to DETECT.
Confidence gating (load-bearing)
Report a finding only at 100% certainty. Anything β€ ~70% goes to VERIFY (step 5) before it can become
a finding β never emit a speculative finding. There is no auto-fix in this domain; everything is
fix_mode: flag-only.
Output contract
Inherits the toolkit's unified contract (full schema + body sections + frontmatter keys:
<swiftui-plugin-root>/references/_shared/finding-schema.md β do not restate it). Specialized for this
domain:
- Findings:
swiftui-audits/document-model/<context>/NN-slug.md(one finding per file, zero-padded, ordered). Per-run index:swiftui-audits/document-model/_index.md. domain: document-model. Frontmatter is the canonical schema;fix_modeisflag-onlyfor every finding.availabilityreads fromfloors-master.md.sourceis an Apple URL + access date (fetched via Sosumi) orverify against Xcode 26 SDK.- Additive field (catalogued, this domain only):
doc_kind=FileDocument|ReferenceFileDocument|DocumentGroup|n/aβ the document shape the finding concerns. Add it alongside the canonical frontmatter; nothing else.
Starter <context> folders (file here whenβ¦):
<context> |
File a finding here when⦠|
|---|---|
phantom-api/ |
a name doesn't exist β @FocusedDocument (doc-01) |
value-vs-reference/ |
the wrong document protocol β class-on-FileDocument, missing snapshot (doc-02, doc-03) |
scene-architecture/ |
NSDocument in SwiftUI, or DocumentGroupLaunchScene with no macOS arm (doc-04, doc-05) |
content-types/ |
a UTType undeclared in Info.plist, or missing writableContentTypes (doc-06, doc-07) |
serialization-safety/ |
serialization on the main actor, or a force-unwrapped FileWrapper (doc-08, doc-09) |
dirty-state-autosave/ |
edits bypass the $document binding (doc-10) |
manual-io/ |
hand-rolled file IO inside a document app (doc-11) |
undo/ |
a reference document with no UndoManager wiring (doc-12) |
New-folder rule: if a finding does not fit any existing context folder, create a new one under
swiftui-audits/document-model/ with a lowercase-hyphen slug naming the sub-category, and note it in the
run's _index.md. Prefer an existing folder when the fit is reasonable; consistency across runs is a hard
requirement. Two runs over the same code produce structurally identical trees.
Reference routing
| File | Open when |
|---|---|
references/document-api-surface.md |
a name/existence question β the real allow-list + the @FocusedDocument β custom-FocusedValues-key rewrite (doc-01) |
references/file-document-model.md |
the value/reference decision, snapshot, FileWrapper, binding-mutation, MainActor serialization, undo (doc-02/03/08/09/10/12) |
references/content-types-utis.md |
readableContentTypes/writableContentTypes, custom UTType, the Info.plist declaration (doc-06/07) |
references/document-scene.md |
DocumentGroup vs NSDocument, DocumentGroupLaunchScene no-macOS-arm, manual IO (doc-04/05/11) |
references/source-directory.md |
step VERIFY β the Apple/WWDC source map fetched via Sosumi |
lint/grep-tells.tsv + lint/ast-grep/*.yml |
step LOCATE β this skill's declarative lint rule set fed to the shared runner (tier-1 grep tells + tier-2 structural ast-grep); edit here to tune detection |
Shared toolkit references (point in, never restate):
| Shared file | For |
|---|---|
<swiftui-plugin-root>/references/_shared/floors-master.md |
every floor/availability value (the reconciled truth) |
<swiftui-plugin-root>/references/_shared/hallucination-blacklist.md |
the canonical invented-name list (incl. @FocusedDocument) |
<swiftui-plugin-root>/references/_shared/macos-arm-gating.md |
the macOS-arm gating rule + wrong-arm/no-arm failure (doc-05) |
<swiftui-plugin-root>/references/_shared/finding-schema.md |
the unified finding schema + frontmatter keys (incl. additive doc_kind) |
<swiftui-plugin-root>/references/_shared/fix-safety-protocol.md |
the fix-safety protocol (step 7 β all flag-only here) |
<swiftui-plugin-root>/references/_shared/sosumi-reference.md |
the Apple-doc spec fetch protocol (step 5 VERIFY) |
<swiftui-plugin-root>/references/_shared/swiftui-ctx-reference.md |
the practice-corpus CLI contract β lookup/deprecated/file --smart for the consensus shape + permalinked example (steps 5 VERIFY Β· 7 FIX) |
<swiftui-plugin-root>/references/_shared/cross-ref-graph.md |
seam ownership + cross_ref targets (doc-04/05/08/11) |
Detection accelerator
bash <swiftui-plugin-root>/scripts/swiftui-lint.sh --skill audit-swiftui-document-model --dir <files-or-dir> [--json out.json] [--sarif out.sarif] β the toolkit's one shared hybrid lint engine, fed this skill's
declarative rules: tier-1 grep tells (lint/grep-tells.tsv, doc-01/02/04/05/06/07/09/10/11/12) +
tier-2 ast-grep structural rules (lint/ast-grep/*.yml β doc-03 ReferenceFileDocument-missing-snapshot,
doc-08 @MainActor-pinned document type) that grep cannot express. It runs a per-file parse probe
(surfaces "did not fully parse" so a structural miss can't look clean), emits unified JSON + SARIF, exits
2 on any hard-fail (doc-01/03/04/05) for a CI gate, and degrades to grep-only with a notice if
ast-grep is unreachable (npx --package @ast-grep/cli ast-grep; faster: brew install ast-grep). It only
LOCATES β always READ each hit in full before reporting (step 3). The thin scripts/doc-lint.sh is a
pointer to this runner. Engine + rule-file format + JSON/SARIF shape + safety rails:
<swiftui-plugin-root>/references/_shared/lint-architecture.md.
Install
Add Audit Swiftui Document Model to your client. Pick the one you use.
npx skills add yigitkonur/plugin-swiftuiInstalls every skill in the repository, then prompts for which to keep.
/plugin marketplace add yigitkonur/plugin-swiftuiAdds the repository as a plugin marketplace; install individual plugins with `/plugin install`.
git clone https://github.com/yigitkonur/plugin-swiftui
cp -r plugins/swiftui/skills/audit-swiftui-document-model ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
80 / 100
Excellent