بنقرة واحدة
fs-skia-keyboard-input
Map keyboard input to product commands in a generated FS.Skia.UI product.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Map keyboard input to product commands in a generated FS.Skia.UI product.
التثبيت باستخدام 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 | fs-skia-keyboard-input |
| description | Map keyboard input to product commands in a generated FS.Skia.UI product. |
Use this skill for product keyboard handling in the app profile: mapping a
normalized ViewerKey (plus its down/up flag) to a product Msg at the host's
MapKey boundary. This is the boundary the generated host actually threads — there
is no separate keyboard reducer to seed.
The signatures you consume are bundled with this product at
docs/api-surface/KeyboardInput/KeyboardInput.fsi (the ViewerKey cases the host
delivers) and docs/api-surface/SkiaViewer/SkiaViewer.fsi (the MapKey: ViewerKey -> bool -> 'msg option field on the generated host). The host normalizes raw key
strings to ViewerKey for you and calls MapKey; your only job is the pure
ViewerKey -> bool -> Msg option mapping.
open FS.Skia.UI.KeyboardInput
// The host calls this at its MapKey boundary: a normalized ViewerKey + down flag
// in, an optional product Msg out. This is the entire consumer keyboard contract.
let mapKey (key: ViewerKey) (isDown: bool) : Msg option =
match key, isDown with
| ArrowLeft, true -> Some MoveLeft
| Space, true -> Some PrimaryAction
| _ -> None
// Wire it into the generated host (app profile):
// let generatedHost = { ... ; MapKey = mapKey ; ... }
The Keyboard.init/Keyboard.update/KeyboardEffect reducer in
KeyboardInput.fsi is an optional advanced surface for products that maintain
their own keyboard state machine; the app host does not use it, so do not
seed it as the consumer path.
ViewerKey.Unknown of raw: string (from FS.Skia.UI.KeyboardInput) and ViewerRunBlockedStage.Unknown
(from FS.Skia.UI.SkiaViewer) are both in scope once you open both modules. A
bare Unknown then binds to whichever module was opened last, producing a
misleading type error far from the real site. Qualify the case at the use site:
match key with
| ViewerKey.Unknown raw -> handleUnknownKey raw // not a bare `Unknown _`
| _ -> ...
The same trap fires across your own co-opened modules — it is not limited to
framework-vs-framework collisions. A consumer that declares both
type GameMode = | Launch | Playing | … and type Msg = | Launch | Tick | …
and opens both has two Launch cases in scope; a bare Launch binds to the
last-declared type (Msg), so a GameMode-typed match arm or constructor
yields ten misleading "expected GameMode but has type Msg" errors far from the
real site. Qualify the case — GameMode.Launch / Msg.Launch — at every use:
let next = GameMode.Launch // not a bare `Launch`
match mode with
| GameMode.Launch -> startGame ()
| _ -> ...
Run ./fake.sh build -t Dev then ./fake.sh build -t Verify in this product.
Run ./fake.sh build -t Test to assert binding resolution and command effects.
Record keyboard command and state evidence under this product's readiness/
paths. Do not copy framework readiness reports into the product.
Keep key reduction pure; the host delivers raw key events and interprets
RequestHostKeyCapture through the viewer, not inside Keyboard.update.
The app profile threads mapKey into generatedHost so the viewer routes input
through your pure reducer.
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.
mapKey.Msg values through the pure adapter.