원클릭으로
fsharp-code-generation
Emit governance artifacts (Markdown/Mermaid/JSON) and generate typed F# source; when NOT to use code quotations.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Emit governance artifacts (Markdown/Mermaid/JSON) and generate typed F# source; when NOT to use code quotations.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Consumer-facing guide to hosting an interactive FS.Skia.UI app — the keyboard/pointer input surface, the preview-vs-tree render distinction, and the windowed-fullscreen blur caveat.
Build Skia-rendered FS.Skia.UI Controls, rich text, chart controls, graph controls, DataGrid, custom wrappers, and generated product examples.
Generated product guidance for Skia-rendered FS.Skia.UI Controls, rich text, chart controls, graph controls, DataGrid, and custom wrappers.
Wire a generated FS.Skia.UI product to the desktop viewer host.
Understand and work with the internal keyed VDOM diff over the lowered Control<'msg> IR (feature 067) — its key-first-then-positional matching, the NodePatch/ChildOp operation set, the totality/determinism/identity-at-rest/round-trip invariants, and the module's disposition (internal, property-tested, wired onto the live render path via RetainedRender in feature 091 and current through feature 103 — layout/bounds cache, injected-delta animation clock, visual-state cross-fade). Use when reading the diff invariants, extending the property tests, or working on the wired retained render path.
Maintainer-facing guide to the FS.Skia.UI.Controls.Elmish interactive-host seam — how runInteractiveApp drives the retained render structure each frame (RetainedRender.step over the keyed diff), advances per-identity animation clocks from an injected Tick delta, stamps runtime visual state pre-reconcile, routes keys focus-first through routeFocusedKey, and resolves pointer hits to a stable identity via retainedHitTest. Use when reading or extending the live controls host loop, the per-frame retained-state/clock/visual-state wiring, or the key/pointer routing seam.
| name | fsharp-code-generation |
| description | Emit governance artifacts (Markdown/Mermaid/JSON) and generate typed F# source; when NOT to use code quotations. |
| compatibility | F# governance library (build/Governance) under net10.0; build-tooling scope only. |
| metadata | {"author":"fs-skia-ui","source":"docs/reports/2026-05-31-1714-foundations-fsharp-capabilities-and-libraries.md"} |
Cookbook for the two different generation jobs in the port — keeping C10/C11 (text) distinct from C12
(F# source) is its main pitfall. Owns C10 (document/text generation — task-graph.md Mermaid +
ASCII tree + count tables), C11 (document generation + currency check — .claude/skills/** from
.agents/skills/**, skillist render), C12 (one-shot F# source generation), and C13 (the
feature-066 single-source product-surface catalog generator — one canonical catalogFacts table
spliced into both catalog.yml and Catalog.fs, drift-guarded by ControlsCatalogGenerationCheck).
Verdicts from the capability report (metadata.source) §5.
task-graph.md (Mermaid graph TD, ASCII └── tree, count tables) and task-graph.json..claude/skills/** from .agents/skills/** and rendering the tasks.md skillist from
tasks.deps.yml, with a currency check that fails when the on-disk copy is stale.capabilities.yml into a typed Config.fs (Stage-3 migration).StringBuilder for
Markdown/Mermaid/ASCII, Utf8JsonWriter for JSON — so output is deterministic and byte-comparable
to the Stage-0 golden fixtures (Invariant 6). FSharp.Formatting / Markdig rejected for emitting
these constrained artifacts.Rendered artifacts MUST be reproducible byte-for-byte (stable ordering, no clock/env in output)
so the golden diff (Invariant 6) is meaningful. Match the Python's exact spacing, connector glyphs
(├── / └── ), and key ordering before deleting it.
graph TD (StringBuilder)open System.Text
let renderMermaid (edges: (string * string) list) =
let sb = StringBuilder()
sb.AppendLine "graph TD" |> ignore
for source, target in edges do
sb.AppendLine(sprintf " %s --> %s" source target) |> ignore
sb.ToString()
open System.Text
let renderTree (root: string) (children: string list) =
let sb = StringBuilder()
sb.AppendLine root |> ignore
let last = List.length children - 1
children
|> List.iteri (fun i child ->
let connector = if i = last then "└── " else "├── "
sb.AppendLine(connector + child) |> ignore)
sb.ToString()
open System.Text
let renderCounts (rows: (string * int) list) =
let sb = StringBuilder()
sb.AppendLine "| Status | Count |" |> ignore
sb.AppendLine "|--------|-------|" |> ignore
for label, count in rows do
sb.AppendLine(sprintf "| %s | %d |" label count) |> ignore
sb.ToString()
Regenerate into memory, diff against the committed copy, fail when they differ. InlineDiffBuilder
over a Differ() gives a line model whose Type flags the drift.
open DiffPlex
open DiffPlex.DiffBuilder
open DiffPlex.DiffBuilder.Model
let private model (current: string) (regenerated: string) =
InlineDiffBuilder(Differ()).BuildDiffModel(current, regenerated)
/// True when the committed copy no longer matches a fresh regeneration.
let isStale (current: string) (regenerated: string) =
(model current regenerated).Lines
|> Seq.exists (fun line -> line.Type <> ChangeType.Unchanged)
/// Readable per-line drift for the failure report.
let staleLines (current: string) (regenerated: string) =
(model current regenerated).Lines
|> Seq.filter (fun line -> line.Type <> ChangeType.Unchanged)
|> Seq.map (fun line -> sprintf "%A | %s" line.Type line.Text)
|> Seq.toList
Utf8JsonWriter)task-graph.json (schema 1.0) is emitted with Utf8JsonWriter for byte-stable layout — the C5
technique from [[fsharp-parsing]]. Keep one renderer so JSON and Markdown stay lock-step with the
golden fixtures.
The single genuine F#-source job is turning capabilities.yml into a typed Config.fs during the
YAML→compiled-F# migration (config ADR D6).
.fsproj (<MyriadFile>), regenerating
every build. Choose only if the catalog must stay as data and be compiled each build.Neither package is in the adopt set, so C12 stays prose — record the chosen path in the Stage-5 ADR before adding the dependency.
The catalog generator is the first product-surface generator and the template every later
single-source generator (design tokens in 069, catalog expansion in 071+) copies. It is the same
single-source discipline as C11 (.claude←.agents) and validation.contract.yml←Routing.fs,
applied to the typed-control catalog. The whole generator is FS.Skia.UI.Build.CatalogGen
(build/Governance/CatalogGen.fsi/.fs); like C10–C12 it is pure rendering + currency over in-memory
text, with every filesystem read/write kept at the Engine/Interpret.fs edge (Principle IV).
One canonical source, two generated artifacts. A single typed value —
catalogFacts : TypedCatalogFact list (51 rows: the six 065-seeded controls plus the 072 expansion) —
is the one place each catalog fact is declared. Two artifacts are generated from it:
catalog.yml (catalogYmlRel) — rendered per row by renderYamlRow, spliced by spliceYaml.Catalog.fs (catalogFsRel) — rendered per row by renderFSharpRow, spliced by spliceFSharp.Splice only the marked regions — never the whole file. Each renderer writes its row into that
control's typed-catalog/<id> marked region (// BEGIN GENERATED: typed-catalog/<id> …
// END GENERATED: typed-catalog/<id>). Every byte outside a marker — the hand-authored rows that
are not part of catalogFacts, the file preamble, unknown marker ids — is left untouched, and the
splice is idempotent on an already-current file. This is what lets a single fact table coexist with
hand-maintained catalog content in the same two files.
Regenerate via RegenerateCatalog, inside RefreshSurfaceBaselines. To add or change a catalog
row, edit catalogFacts (and the typed module it names), then run
./fake.sh build -t RefreshSurfaceBaselines — which runs RegenerateCatalog to re-splice both
artifacts. Never hand-edit a generated typed-catalog/<id> region: the drift gate will fail it.
The drift gate: ControlsCatalogGenerationCheck. It re-renders catalogFacts in memory and
compares each on-disk region against the fresh render via currency : catalogYmlText -> catalogFsText -> CatalogCurrency list (each region is Current/Stale/Missing); the file set is clean iff
isCurrent is true. On drift, currencyDrift returns one actionable line per stale/missing region,
naming the divergent control, the file, and ./fake.sh build -t RefreshSurfaceBaselines as the fix —
so a hand-edited generated region (or a catalogFacts change with no regen) fails the gate with a
pointer at the regeneration target, exactly like C11's isStale/staleLines currency check.
Cross-check against the typed surface. Each row's Module and RequiredAttributes facts are
cross-checked against the FS.Skia.UI.Controls.Typed surface, so the catalog cannot claim a typed
control or required attribute the typed front door does not actually expose. (Authoring that typed
surface is [[fs-skia-typed-controls]]'s job; this generator records and drift-guards the catalog facts
about it.)
open FS.Skia.UI.Build
// Single source: one fact per typed control. (abbreviated — real rows carry purpose/events/role)
let catalogFacts : CatalogGen.TypedCatalogFact list =
[ { Id = "button"; DisplayName = "Button"; Category = "input"; Module = "Button"
Purpose = "Pointer and keyboard activatable command."
RequiredAttributes = [ "text" ]; Events = [ "onClick" ]; AccessibilityRole = "Button" }
// … 50 more rows … ]
// Regeneration re-splices ONLY the typed-catalog/<id> regions of both files.
let regeneratedFs = CatalogGen.spliceFSharp (System.IO.File.ReadAllText CatalogGen.catalogFsRel)
let regeneratedYml = CatalogGen.spliceYaml (System.IO.File.ReadAllText CatalogGen.catalogYmlRel)
// Drift gate: clean iff every region matches a fresh render; else currencyDrift names the fix.
let regions = CatalogGen.currency regeneratedYml regeneratedFs
if not (CatalogGen.isCurrent regions) then
CatalogGen.currencyDrift regions // e.g. "catalog.yml is stale — its typed-catalog/button region … Regenerate via ./fake.sh build -t RefreshSurfaceBaselines."
|> List.iter (eprintfn "%s")
F# code quotations (<@ … @>) are runtime metaprogramming producing Expr trees evaluated at run
time — they do not emit source or build artifacts, and would re-introduce the runtime-evaluation
tax the foundations programme is removing (config ADR D6, no-FCS stance). Do not use them for the
governance port; noted only to prevent the common conflation with source generation.
The document renderers now ship as the real CodeGen module in the FS.Skia.UI.SkillSupport
package — its .fsi surface lives at src/SkillSupport/CodeGen.fsi. FS.Skia.UI.Build consumes the
same code via ProjectReference, so the governance build renders through exactly these functions:
CodeGen.mermaidGraph — render graph TD Mermaid from edge pairs.CodeGen.markdownTable — render a Markdown count/status table from header + rows.CodeGen.asciiTree — render the ├── /└── ASCII dependency tree (stable connector glyphs).open FS.Skia.UI.SkillSupport
let edges = [ ("T001", "T002"); ("T002", "T003") ]
let mermaid = CodeGen.mermaidGraph edges // "graph TD\n T001 --> T002\n ..."
let table = CodeGen.markdownTable [ "Status"; "Count" ] [ [ "done"; "3" ]; [ "pending"; "1" ] ]
let tree = CodeGen.asciiTree "root" [ "T001"; "T002" ] // root\n├── T001\n└── T002
Config.fs is real source generation.Stage 2 (GenerateAgentSkills + skillist render + currency check), Stage 4 (task-graph.md /
task-graph.json render), Stage 3.3/5.5 (the one-shot Config.fs generation). See the plan in
metadata.source.
When a problem outlasts reasonable in-repo attempts, extensive external research is
mandatory — consult official online docs first (the F#/.NET docs and the driven
library's own documentation/API reference), then community sources (forums, Reddit, Q&A
sites, issue trackers and changelogs). Record the findings and resolving links in the
feature's specs/<feature>/feedback/ folder and, for durable lessons, in this skill's
Sources line. Offline, the mandate degrades to recording "research blocked — "
rather than hard-failing the phase.
Utf8JsonWriter: https://learn.microsoft.com/dotnet/api/system.text.json.utf8jsonwriterdocs/reports/2026-05-31-1714-foundations-fsharp-capabilities-and-libraries.md §5.[[fsharp-parsing]], [[fsharp-graph-algorithms]] (produce the model rendered here),
[[fsharp-io-globbing]] (currency-check diffing), [[fsharp-build-orchestration]] (DiffPlex also backs
the golden-parity gate), [[fs-skia-typed-controls]] (authors the typed front door that C13's
catalogFacts Module/required-attribute facts are cross-checked against; typed authoring is the
preferred front door it owns).