diff --git a/.circleci/config.yml b/.circleci/config.yml index 567c374f48c5..a1a8774ae627 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -130,8 +130,9 @@ jobs: - project/node_modules - project/packages - project/scripts - # Playground unit tests (transformCode, codeModel); rest of website omitted + # Playground and DemoVideo unit tests; rest of website omitted - project/website/src/components/Playground + - project/website/src/components/DemoVideo - project/.yarnrc.yml - project/babel.config.js - project/eslint.config.mjs diff --git a/.cursor/skills/design-capability-demos/SKILL.md b/.cursor/skills/design-capability-demos/SKILL.md new file mode 100644 index 000000000000..0551df6611ae --- /dev/null +++ b/.cursor/skills/design-capability-demos/SKILL.md @@ -0,0 +1,161 @@ +--- +name: design-capability-demos +description: Design, compare, rank, and refine demonstration concepts for framework or library capabilities. Use when Codex needs to turn a capability, API, architectural property, performance feature, developer-tool feature, safety guarantee, recovery behavior, or before/after implementation into a demo scenario with a credible contrast, minimal trigger, direct evidence, and tuned parameters or perturbations. Focus strictly on the demo mechanism itself, not narration, documentation, slides, launch material, or explanatory presentation. +--- + +# Design Capability Demos + +Design an observable proof of a capability. Produce 3-5 ranked concepts, then refine the strongest by tuning or varying the conditions under which the capability matters. + +## Maintain the boundary + +Include only: + +- scenario and objects in the demo +- baseline and capability-enabled variants +- controlled condition and fixed variables +- user action, edit, fault, invalid operation, or automatic trigger +- direct evidence of the claimed property +- parameter values, structural perturbations, ranges, and tuning plan +- minimal instrumentation needed to perceive or verify the evidence + +Exclude narration, disclosure, teaching copy, slide structure, marketing claims, audience setup, and surrounding presentation. Do not solve a weak demo with explanation. + +## Core model + +Represent each demo as: + +`structurally fitting scenario + leverage condition + credible contrast + minimal trigger -> unmistakable evidence` + +Preserve one causal chain: + +`capability -> changed property -> evidence on the same outcome surface` + +Define before ideating: + +- **Capability:** the property uniquely enabled or substantially improved. +- **Outcome surface:** where the genuine value appears, such as runtime behavior, failure recovery, compile-time prevention, authoring locality, generated artifacts, diagnostics, safety, or interoperability. +- **Baseline:** the strongest ordinary implementation without that capability. +- **Leverage condition:** the workload, dependency shape, failure, invalid operation, ownership boundary, platform difference, or other condition that makes the property evident. +- **Invariant:** the general relationship the demo reveals without framework or scenario nouns. +- **Evidence:** the state, behavior, diagnostic, artifact change, prevented invalid state, or recovery result that directly supports the claim and reaches its stated terminal condition. + +Read [references/principles.md](references/principles.md) when the capability is abstract, candidate scenarios are weak, or structural fit is uncertain. Read [references/evaluation-and-failures.md](references/evaluation-and-failures.md) when ranking close candidates, auditing a draft, or diagnosing a demo that feels impressive but unconvincing. + +## Workflow + +### 1. Normalize the capability + +Inspect available API documentation, code, benchmarks, or user context when provided. Write: + +```text +Capability: +Outcome surface: +Without it: +With it: +Leverage condition: +Direct evidence: +Invariant: +Confounders to hold fixed: +``` + +Separate the capability from API syntax and implementation mechanism. Preserve its outcome surface: do not turn an authoring, architectural, diagnostic, safety, or interoperability claim into a runtime-performance claim because speed is easier to show. If the advantage lacks direct evidence on its genuine surface, state that it is not yet demo-ready and identify the missing causal link. + +Choose the nearest credible baseline before selecting a scenario. Use the best ordinary alternative a competent practitioner would actually consider. Reject a comparison that removes unrelated batching, caching, validation, composition, or optimization merely to make the capability win. + +### 2. Find the high-leverage regime + +Identify what controls how much the capability matters. It may be: + +- numeric: scale, latency, rate, cost, contention, or failure frequency; +- structural: dependency depth, ownership boundaries, change fan-out, or coordination count; +- categorical: valid versus invalid operation, success versus injected failure, or one environment versus another. + +Do not invent a numeric scale for an inherently discrete claim. Choose conditions where: + +- the baseline exposes the exact limitation the capability addresses; +- the enabled case shows the claimed property on the same outcome surface; +- the condition is plausible for the scenario; +- the advantage comes from amplifying or activating a real condition, not adding an unrelated penalty; +- neither side hits an unrelated bottleneck or changes useful work. + +For numeric variables, find a broad clarity window. For structural or categorical variables, choose the smallest discriminating case plus an adjacent or boundary case that could falsify an overbroad claim. + +### 3. Generate 3-5 distinct concepts + +Create candidates that differ in proof structure or structurally fitting scenario, not cosmetic theme. Keep every candidate on the declared outcome surface; do not invent a different benefit. + +For each candidate provide one compact row containing: + +```text +Concept | Structural fit | Outcome-surface fidelity | Credible contrast | +Trigger | Direct evidence | Leverage variable/perturbation | Main confounder +``` + +Use familiar experiences when they naturally instantiate the capability. Prefer realistic causal relationships and constraints over product decoration. Remove any element that does not create the condition, activate the property, or expose the evidence. + +### 4. Rank the candidates + +Score each concept from 1-5 on: + +- **Capability isolation:** the capability is the only plausible cause. +- **Outcome-surface fidelity:** the evidence proves the genuine claim without substituting a more dramatic benefit. +- **Baseline credibility:** the alternative is the nearest competent implementation, not a straw baseline. +- **Contrast strength:** the outcomes are unmistakably different. +- **Interpretation cost:** the evidence maps to the claim with little inference. +- **Structural fit:** the scenario naturally contains the relevant condition. +- **Transfer:** the invariant is visible beyond the chosen objects. +- **Robustness and retellability:** the result survives reasonable variation and retains a compact causal grammar. + +Weight the first three twice. Reject a concept regardless of total score if it changes the claim's surface, relies on a confounder or contrived handicap, or compares against an avoidably weak alternative. + +Return one compact row per candidate with a total and one short rationale. Choose one canonical concept. Do not fully specify losing candidates. + +### 5. Refine the winner by varying leverage + +Turn the winner into an executable specification: + +1. Identify the primary leverage variable or perturbation and secondary realism controls. +2. For a numeric variable, establish the baseline threshold and enabled limit, then select a target inside the clarity window. +3. For a structural or categorical variable, choose the smallest discriminating case and test adjacent or boundary cases. +4. Verify that the evidence changes only when the claimed property is activated or violated, and proves the claimed end state rather than an intermediate mechanism event. +5. Hold useful work, inputs, final intended result, environment, and unrelated optimizations constant unless the capability specifically changes one. +6. Remove controls, metrics, and visuals not needed to perceive or verify the evidence. + +Use: + +| Variable or perturbation | Causal purpose | Tested cases/range | Chosen case | Too weak / adjacent case | Confounding boundary | +|---|---|---|---|---|---| + +If empirical testing is available, sweep numeric variables or test a small perturbation matrix. Optimize for stable discrimination, not the largest number or most dramatic failure. + +### 6. Run the counterfactual audit + +Verify all answers are yes: + +- If the capability is removed and nothing else changes, does the advantage disappear? +- Does the evidence stay on the declared outcome surface? +- Is the baseline the strongest credible alternative without the capability? +- Does the trigger exercise the claimed property directly? +- Does the evidence reach the claim's terminal condition rather than stop at an intermediate event? +- Do both variants pursue equivalent useful work or the same intended result? +- If decorative labels are hidden, does the behavior, diagnostic, artifact, or state still support the result? +- Is the scenario believable because of its causal structure? +- Can the demo be reduced further without weakening the proof? +- Does the chosen range or perturbation survive an adjacent or boundary case? +- Does the invariant transfer to other domains? + +Revise or discard the concept when any causal answer is no. + +## Output contract + +Return, in order: + +1. **Capability model** using the fields from step 1. +2. **Ranked concepts** with 3-5 compact rows and weighted totals. +3. **Selected concept** with a one-sentence selection rationale. +4. **Refined demo specification** containing scenario, credible baseline, enabled variant, trigger, direct evidence, fixed controls, and leverage table. +5. **Validity audit** listing only failed or uncertain audit items and remaining confounders. + +Keep the output about demo design. Do not add presentation advice or repeat the same rationale across sections. + diff --git a/.cursor/skills/design-capability-demos/agents/openai.yaml b/.cursor/skills/design-capability-demos/agents/openai.yaml new file mode 100644 index 000000000000..e025808050eb --- /dev/null +++ b/.cursor/skills/design-capability-demos/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Design Capability Demos" + short_description: "Generate and refine compelling capability demos" + default_prompt: "Use $design-capability-demos to generate and rank demo concepts for this framework or library capability." + diff --git a/.cursor/skills/design-capability-demos/evals/evals.json b/.cursor/skills/design-capability-demos/evals/evals.json new file mode 100644 index 000000000000..5c7c6c193877 --- /dev/null +++ b/.cursor/skills/design-capability-demos/evals/evals.json @@ -0,0 +1,113 @@ +{ + "skill_name": "design-capability-demos", + "evals": [ + { + "id": 1, + "name": "react-deferred-rendering", + "prompt": "We're trying to show why React's useDeferredValue is useful. It lets an expensive, non-urgent render lag behind so urgent input updates stay responsive, and background rendering can be interrupted when newer input arrives. Come up with demo ideas, rank them, and refine the strongest one into something we could build.", + "expected_output": "A compact set of ranked concepts culminating in a controlled same-workload demo where a familiar urgent interaction remains responsive while expensive dependent rendering lags and catches up. Parameters should amplify genuine render cost, identify a stable clarity window, and avoid penalizing only the baseline.", + "files": [], + "assertions": [ + "The response provides 3-5 genuinely distinct demo concepts and explicitly ranks them.", + "The selected design compares equivalent useful work and changes only use of deferred rendering or its closest credible activation difference.", + "A familiar, minimal interaction directly exposes urgent input responsiveness versus blocking, while the expensive dependent result eventually catches up.", + "The workload parameters amplify genuine render cost rather than inserting a delay or handicap only into the baseline.", + "The refined design identifies a primary parameter, a usable range or clarity window, fixed controls, and failure conditions when tuned too low or too high.", + "The response identifies a transferable invariant without relying on React-specific nouns.", + "The response stays focused on scenario, contrast, interaction, observable outcome, controls, and parameters rather than presentation or explanatory messaging." + ] + }, + { + "id": 2, + "name": "relay-colocated-fragments", + "prompt": "Design a demo for Relay fragments. The capability is that each nested UI component can declare its own data requirements independently, while Relay composes those fragments into one efficient operation. We want several candidate concepts, a ranked choice, and a refined demo design—not implementation code.", + "expected_output": "A structurally fitting nested-component scenario that makes ownership and composition observable, likely through a local child requirement change whose effects remain local in source while the generated operation updates. The comparison should preserve equivalent data and UI output and should tune structural parameters such as nesting, component count, or requirement changes without burying the idea in app chrome.", + "files": [], + "assertions": [ + "The response provides 3-5 distinct concepts, ranks them, and selects one canonical case.", + "The selected scenario naturally contains nested components with independently owned data requirements.", + "The contrast preserves equivalent UI and fetched data while changing centralized versus colocated declaration and composition.", + "A small local change to a child component's data needs produces an observable consequence that demonstrates both local ownership and composition into an efficient operation.", + "The design avoids claiming a runtime speedup when the demonstrated benefit is architectural or authoring-related.", + "The refined design tunes causally relevant structural parameters and records fixed controls or confounders.", + "The design removes incidental product complexity and exposes a transferable invariant about local requirements and global composition." + ] + }, + { + "id": 3, + "name": "container-query-layout", + "prompt": "hey, can you design a few strong demo ideas for a component library feature built on CSS container queries? Components adapt to the width they're actually given instead of the browser viewport. Rank the ideas and tune the winning one so the difference from viewport-based breakpoints is impossible to miss.", + "expected_output": "A familiar component placed in independently sized containers under one fixed viewport, with a minimal resizing or layout-change interaction. The controlled baseline uses viewport breakpoints, the enabled case uses container width, and selected widths cross component-relevant thresholds without changing content or viewport. The result should transfer beyond the chosen component type.", + "files": [], + "assertions": [ + "The response provides and ranks 3-5 structurally distinct concepts before refining one.", + "The selected demo holds the viewport, component content, and useful output constant while varying container allocation and query mechanism.", + "One simple resize or layout-allocation interaction makes independently correct component adaptation directly visible.", + "The design selects container widths or breakpoints that create a broad, robust contrast rather than a fragile single threshold.", + "The selected scenario is structurally realistic for reusable components embedded in different parent layouts.", + "The response identifies confounders such as viewport changes, different content, CSS differences beyond the query mechanism, or layout overflow.", + "The invariant generalizes beyond the specific component or product surface used in the demo." + ] + }, + { + "id": 4, + "name": "postgres-transaction-atomicity", + "prompt": "Design several demo concepts for PostgreSQL transaction atomicity. The capability is that a multi-step operation becomes visible all at once or not at all: for example, transferring money requires debiting one account and crediting another, and a failure between those updates must not leave a partial transfer. Rank the concepts and refine the strongest one without drifting into a throughput benchmark.", + "expected_output": "A canonical bank-transfer or equivalent invariant-preserving scenario using the same dependent updates and a deterministic failure at the same point. The credible baseline commits statements independently and can expose partial state; the transaction wraps them so failure rolls everything back. Direct evidence should be the balances and conserved invariant, with success and failure perturbations rather than artificial scale.", + "files": [], + "reference_sources": [ + "https://www.postgresql.org/docs/current/tutorial-transactions.html" + ], + "assertions": [ + "The response provides and ranks 3-5 structurally distinct concepts before refining one.", + "The selected design keeps the genuine outcome surface on atomic visibility and invariant preservation rather than claiming a throughput or latency advantage.", + "The credible contrast uses the same intended multi-step operation and failure point, varying independent commits versus one transaction rather than weakening the baseline with unrelated behavior.", + "One deterministic failure injected after an early mutation directly exposes partial state in the baseline and rollback or non-visibility in the transactional case.", + "The observable state includes an invariant such as conserved total value, not merely a success label or transaction log message.", + "The refinement tests at least a successful operation, the discriminating mid-operation failure, and a boundary or adjacent failure case while controlling initial data, autocommit behavior, retries, and concurrency.", + "The invariant transfers to dependent multi-step mutations beyond banking and the response remains within demo-design scope." + ] + }, + { + "id": 5, + "name": "rust-ownership-static-prevention", + "prompt": "Come up with and rank demo concepts for Rust ownership and moves as a memory-safety capability. Focus on the fact that after ownership of a heap value moves, the old binding cannot be used, so invalid access is rejected at compile time. Refine the best demo itself; don't turn this into a language-speed comparison or a general Rust tutorial.", + "expected_output": "A minimal invalid program moves a String and then uses the old binding, producing a compile-time diagnostic before execution, paired with nearby valid ownership choices such as using the new owner, borrowing, or explicitly cloning. The proof should remain on static prevention, avoid contrived crashes or performance claims, and vary the ownership relation rather than adding scale.", + "files": [], + "reference_sources": [ + "https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html", + "https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html" + ], + "assertions": [ + "The response provides and ranks 3-5 distinct proof concepts before refining one canonical case.", + "The selected design preserves the compile-time safety outcome surface and does not substitute runtime speed, garbage-collection, or contrived crash claims.", + "A minimal move followed by use of the invalidated binding is the discriminating trigger, with the compiler diagnostic as direct evidence before execution.", + "The contrast or perturbation matrix includes nearby valid programs that preserve the same intended ownership task, such as using the new owner, borrowing, or explicit cloning.", + "Any cross-language or unchecked comparator is limited to the acceptance-versus-rejection claim and does not attribute differences caused by syntax, runtime, or unrelated tooling to ownership.", + "The design uses structural or categorical boundary cases rather than inventing scale, latency, or benchmark parameters.", + "The invariant generalizes to preventing invalid use after transfer of exclusive responsibility, and the response avoids tutorial or presentation material." + ] + }, + { + "id": 6, + "name": "kubernetes-replica-self-healing", + "prompt": "Design and rank several demos for Kubernetes self-healing through desired-state reconciliation. Specifically, when a Pod belonging to a Deployment fails or is deleted, the controller creates a replacement to restore the desired replica count. Refine the strongest demo and keep the claim narrower than 'Kubernetes fixes applications.'", + "expected_output": "A controlled failure of one managed replica with direct observation of desired, current, and ready state plus replacement identity. A credible unmanaged or manually recovered baseline uses the same container and failure. The design distinguishes replica replacement from container restart and avoids implying that reconciliation repairs application defects or guarantees uninterrupted service.", + "files": [], + "reference_sources": [ + "https://kubernetes.io/docs/concepts/architecture/self-healing/", + "https://kubernetes.io/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/" + ], + "assertions": [ + "The response provides and ranks 3-5 structurally distinct concepts before refining one.", + "The selected design keeps the outcome surface on restoration of declared replica state, not broad application repair, generic high availability, or performance.", + "The baseline and enabled case use the same container, health behavior, and failure while varying credible manual or unmanaged recovery versus controller reconciliation.", + "One explicit Pod failure or deletion reveals a temporary replica deficit followed by a new replacement identity and restoration of the desired ready count.", + "The design distinguishes Deployment replica replacement from a kubelet restarting a container inside the same Pod and controls restart policy or failure type accordingly.", + "The refinement tests a no-failure case, one discriminating failure, and a bounded adjacent case while controlling probes, image, desired replicas, scheduling capacity, and observation timing.", + "The invariant transfers to reconciliation systems generally, and caveats prevent claims about fixing persistent application bugs or guaranteeing zero interruption." + ] + } + ] +} + diff --git a/.cursor/skills/design-capability-demos/references/evaluation-and-failures.md b/.cursor/skills/design-capability-demos/references/evaluation-and-failures.md new file mode 100644 index 000000000000..584d9ebf3c2a --- /dev/null +++ b/.cursor/skills/design-capability-demos/references/evaluation-and-failures.md @@ -0,0 +1,137 @@ +# Evaluation and failure diagnosis + +Use this reference to rank close candidates, audit a design, or repair a weak result. + +## Weighted rubric + +Score each criterion from 1-5. Multiply the first three by 2, for a maximum of 55. + +| Criterion | 1 | 3 | 5 | Weight | +|---|---|---|---|---:| +| Claim-surface fidelity | Evidence proves a different benefit | Surface is partly preserved | Evidence directly proves the genuine claim | 2 | +| Counterfactual credibility | Straw or obsolete baseline | Plausible but avoidably weak | Strongest ordinary alternative without the capability | 2 | +| Capability isolation | Several plausible causes | Main cause is likely but not exclusive | Only the capability explains the delta | 2 | +| Contrast strength | Difference is marginal | Difference is noticeable | Difference is immediate and categorical | 1 | +| Interpretation cost | Requires extensive inference | Requires brief inspection | Evidence maps directly to the claim | 1 | +| Structural fit | Condition is arbitrary | Condition is plausible | Scenario naturally creates the condition | 1 | +| Transfer | Bound to scenario trivia | Pattern can be abstracted | Invariant is obvious across domains | 1 | +| Robustness and retellability | Fragile and hard to reconstruct | Survives limited variation | Stable evidence with compact causal grammar | 1 | + +Reject any candidate below 3 on a weighted criterion. Prefer claim fidelity and baseline credibility when totals are close. + +## Hard gates + +A valid design must satisfy all gates: + +1. Both variants pursue equivalent useful work or the same intended result. +2. Evidence remains on the capability's genuine outcome surface. +3. The baseline is the nearest credible alternative and retains unrelated standard optimizations. +4. The leverage condition belongs to the capability's operating regime. +5. The capability is necessary for the demonstrated advantage under fixed conditions. +6. Evidence reaches the claim's terminal condition rather than only showing an intermediate mechanism event. +7. Adjacent parameter values or structural perturbations preserve or appropriately bound the claim. + +## Anti-patterns and repairs + +### Intermediate mechanism as proof + +**Symptom:** The demo shows scheduling, generation, replacement, rollback, or validation starting, but not the healthy, valid, restored, or accepted state named by the claim. + +**Repair:** Observe the terminal condition and its invariant. If only the intermediate event is demonstrable, narrow the claim to that event. + +### Outcome-surface substitution + +**Symptom:** An architectural, authoring, diagnostic, safety, or portability capability is presented as a speedup or runtime failure. + +**Repair:** Name the outcome surface first, then choose direct evidence on it: edit propagation, generated artifacts, rejected invalid states, diagnostics, recovery state, or environment equivalence. + +### Straw baseline + +**Symptom:** The baseline loses because it omits ordinary batching, caching, validation, composition, or another independent practice. + +**Repair:** Upgrade it to the strongest ordinary implementation without the capability. If the advantage disappears, narrow the claim instead of weakening the baseline. + +### Fabricated handicap or unequal work + +**Symptom:** One side receives a delay, extra loop, worse data, missing optimization, or less useful output. + +**Repair:** Equalize intended results, data, side effects, and independent optimizations. Amplify only a condition the capability addresses. + +### Proxy-only outcome + +**Symptom:** A chart, counter, or log substitutes for an experience even though the claim is experiential, or an experience substitutes for a static guarantee. + +**Repair:** Match direct evidence to the declared outcome surface. Use instrumentation only to expose an otherwise invisible property on that surface. + +### Scenario-first ideation + +**Symptom:** A familiar app is selected, then arbitrary behavior is inserted to make the capability relevant. + +**Repair:** Define capability, surface, baseline, leverage condition, and invariant before selecting a scenario. + +### Surface-realistic miniature app + +**Symptom:** Navigation, styling, content, and chrome outnumber causally relevant elements. + +**Repair:** Preserve realistic causal structure and remove decorative completeness. + +### Toy without structural realism + +**Symptom:** The case is simple but no plausible system encounters its condition. + +**Repair:** Restore the smallest credible scale, concurrency, failure, dependency, invalid operation, ownership boundary, or platform difference. + +### Many triggers, many conclusions + +**Symptom:** Several controls reveal different benefits and require remembering earlier states. + +**Repair:** Choose one invariant and one canonical trigger. Split separate claims into separate demos. + +### Knife-edge tuning + +**Symptom:** A numeric result flips with small device, timing, or data changes. + +**Repair:** Sweep the primary variable, map the clarity window, and choose away from both boundaries. + +### Forced numeric tuning + +**Symptom:** Arbitrary scale, latency, or counts are added to a binary or structural capability. + +**Repair:** Use the smallest discriminating case plus adjacent and boundary perturbations. Tune numbers only when magnitude genuinely controls leverage. + +### Catastrophic overload + +**Symptom:** Both variants fail or an unrelated subsystem becomes the bottleneck. + +**Repair:** Reduce or isolate the workload until the enabled property remains functional. + +### Hidden preconditions + +**Symptom:** Cache state, timing coincidence, randomness, hardware, prior actions, or environment quirks drive the result. + +**Repair:** Fix or expose preconditions, use deterministic inputs where possible, and test the intended environment range. + +### API showcase instead of capability proof + +**Symptom:** Source syntax changes but no claimed property is evidenced. + +**Repair:** Trace the API to its genuine outcome surface. For authoring ergonomics, compare equivalent changes and observe edit locality, invalidation scope, diagnostics, or generated artifacts rather than inventing runtime effects. + +### Domain-locked lesson + +**Symptom:** The concept proves scenario trivia rather than a general capability. + +**Repair:** State the invariant without scenario nouns and redesign until the objects instantiate it cleanly. + +## Leverage audit + +For each variable or perturbation, record: + +- causal role and realistic source; +- expected effect on each variant; +- lower/adjacent case where evidence is insufficient; +- upper/boundary case where the claim stops or a confounder appears; +- sensitivity to environment, randomness, and warmup when relevant. + +For numeric variables, choose inside the clarity window. For discrete claims, use a small perturbation matrix rather than artificial scale. + diff --git a/.cursor/skills/design-capability-demos/references/principles.md b/.cursor/skills/design-capability-demos/references/principles.md new file mode 100644 index 000000000000..aa595e869f3c --- /dev/null +++ b/.cursor/skills/design-capability-demos/references/principles.md @@ -0,0 +1,122 @@ +# Design principles and concept hierarchy + +Use this reference when a capability is abstract or structural fit is uncertain. Apply the hierarchy top to bottom. + +## 1. Reveal the capability + +Make a unique property directly evident. Start with the delta, not a product theme, API call, or feature list. + +Ask: + +- What becomes possible, remains true, or is prevented because of the capability? +- What evidence would remain if promotional labels disappeared? +- Which nearby capability could be mistaken for the cause? + +The evidence must be a consequence of the capability, not a convenient proxy. + +Follow the claim to its terminal condition. A scheduled replacement is not yet a recovered healthy service; emitted code is not yet a valid composed artifact; starting rollback is not yet restored invariant state. Stop earlier only when the claim is explicitly limited to that intermediate mechanism. + +## 2. Preserve the outcome surface + +Identify where the capability genuinely creates value before choosing how to show it. Runtime behavior is only one surface. Others include prevented invalid states, recovery after failure, localized source changes, generated artifacts, diagnostics, safety guarantees, and cross-environment consistency. + +Keep the proof on that surface. Demonstrate ownership through ownership and change propagation; a compile-time guarantee through accepted and rejected programs; recovery through state before, during, and after failure. Do not invent a speedup, request reduction, or visual effect to make a quieter capability dramatic. + +## 3. Place it in its high-leverage regime + +Capabilities matter under conditions. Identify the controlling condition and move far enough into it that the difference is unmistakable. + +Amplify or activate a condition already present in legitimate use: + +- more items for scaling behavior; +- slower dependencies for latency behavior; +- concurrent actors for coordination; +- a credible injected failure for recovery; +- a minimal invalid operation for static prevention; +- a local requirement change for ownership or composition; +- an actual environment boundary for portability. + +Do not slow only the baseline, omit equivalent work, change data, or disable an unrelated optimization. + +## 4. Establish the nearest credible contrast + +Use the strongest ordinary alternative a competent practitioner would choose without the capability. Keep constant everything except the capability or the smallest difference that activates it. + +If the alternative can recover the same advantage with a standard independent optimization, include that optimization. A weak baseline proves only that the baseline was weak. + +Useful contrast forms include: + +- same workload, synchronous versus interruptible processing; +- same multi-step mutation, individually committed versus atomic; +- same invalid program, unchecked execution versus compile-time rejection; +- same local requirement change, centralized ownership versus local ownership plus derived composition; +- same fault, unmanaged state versus reconciled desired state. + +Choose side-by-side, toggle, artifact diff, or repeated trial based on the evidence surface and memory burden. + +## 5. Minimize interpretation + +Match evidence to the surface: + +- direct experience for experiential claims; +- diagnostics for static prevention; +- artifact or dependency diffs for composition; +- state transitions for recovery; +- invariant checks for atomicity or safety; +- environment matrices for portability. + +Instrumentation is valid when it is direct evidence, not a proxy for a different claim. + +## 6. Remove incidental complexity + +Every element must create the leverage condition, activate the property, or expose the evidence. Remove it otherwise. Complexity is justified only when part of the causal structure. + +## 7. Choose structural fit + +Match the scenario to the capability's causal shape rather than an industry label. + +| Capability structure | Structurally fitting scenario traits | +|---|---| +| Scheduling or prioritization | urgent and deferrable work compete after one action | +| Caching or memoization | repeated expensive work with stable identity | +| Incremental computation | a small change affects a bounded part of a larger result | +| Streaming or progressive delivery | independent parts become available at different times | +| Resilience or recovery | a credible failure interrupts valuable state or availability | +| Transactions or atomicity | dependent mutations must succeed or fail together | +| Reactive propagation | a source change flows through a dependency graph | +| Static safety or validation | a minimal invalid operation is rejected before execution | +| Ownership or composition | a local change updates its owner and the derived aggregate | +| Interoperability or portability | equivalent input crosses a real environment boundary | + +## 8. Use familiar concrete experiences + +Choose an action with a known success condition: typing, dragging, transferring, saving, compiling, failing, recovering, or changing a local requirement. Familiarity reduces interpretation but never outranks structural fit. + +## 9. Prefer one canonical case deeply + +After comparing alternatives, deepen one case until its contrast and controls are robust. Multiple shallow vignettes dilute causal proof. Use phases only when the invariant unfolds over time. + +## 10. Expose an invariant for transfer + +State the relationship without framework, API, or scenario nouns. Examples: + +- urgent work remains responsive while deferrable work progresses; +- dependent mutations become visible together or not at all; +- invalid aliasing is rejected before execution; +- recoverable desired state is restored after a replaceable member fails; +- local requirements compose into a global artifact without duplicating ownership. + +If the invariant cannot be stated cleanly, the concept may demonstrate scenario trivia. + +## 11. Favor structural realism over surface realism + +Preserve realistic causal relations, workload shapes, failure modes, and constraints. Product chrome and production feature breadth are optional and usually harmful. + +## 12. Optimize retellability through design + +Use a compact causal grammar: + +`When [trigger] occurs under [condition], baseline [evidence] while enabled [evidence].` + +If reconstruction requires exceptions, several metrics, or hidden setup facts, simplify the demo. + diff --git a/GOALS.md b/GOALS.md index fd863dc91506..0c73f615855c 100644 --- a/GOALS.md +++ b/GOALS.md @@ -10,8 +10,8 @@ - Intuitive to agents and humans; skills and codemods for onboarding, debugging, migrations - Data binding directly in the component that renders the data - Strong data integrity guarantees + - ACID over async durable stores, plus reactivity — clear cause and effect, without losing work - Referential stability: unchanged data keeps the same object identity everywhere - - Atomic mutations; every view consistent without refetching - Types and runtime never silently diverge - Best performance in class - Networking overhead is the most expensive - minimize this first diff --git a/docs/core/concepts/acid.md b/docs/core/concepts/acid.md new file mode 100644 index 000000000000..787ffcdc54d4 --- /dev/null +++ b/docs/core/concepts/acid.md @@ -0,0 +1,244 @@ +--- +title: 'ACID: Integrity for frontend data' +sidebar_label: ACID +description: Atomic, consistent, isolated, durable async data — using the server as the store. +--- + + + + + +import AcidUpdate from '../shared/\_acidUpdate.mdx'; +import AcidCreate from '../shared/\_acidCreate.mdx'; +import AcidDelete from '../shared/\_acidDelete.mdx'; +import AcidRollback from '../shared/\_acidRollback.mdx'; +import AcidSideEffects from '../shared/\_acidSideEffects.mdx'; +import AcidIdentity from '../shared/\_acidIdentity.mdx'; +import AcidCollections from '../shared/\_acidCollections.mdx'; +import AcidQuery from '../shared/\_acidQuery.mdx'; +import AcidValidate from '../shared/\_acidValidate.mdx'; +import AcidTransports from '../shared/\_acidTransports.mdx'; +import OptimisticTransform from '../../rest/shared/\_optimisticTransform.mdx'; +import AcidSnapshot from '../shared/\_acidSnapshot.mdx'; +import AcidRest from '../shared/\_acidRest.mdx'; + +# ACID for frontend data + +Users expect **clear cause and effect**: actions have consequences, and those +consequences are obvious. Things should not appear, disappear, or change +on their own. A user's time is valuable — don't lose their work. + +[Relational databases](https://en.wikipedia.org/wiki/ACID) call these guarantees +ACID. The frontend store is that database for interactive data — but every +durable write is [asynchronous](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous). + +Reactive Data Client applies the same guarantees so every view agrees without +refetching, mutations don't flash torn state, and +crashes don't lose data that reached a durable store like a REST server or +[IndexedDB](./managers.md#persistence). + +[Normalization](./normalization.md) is what makes this possible. + +## Atomicity + +A mutation is a single unit: it succeeds completely or fails completely. +Other components never observe it halfway. That prevents *temporal data +tearing* — flashes of inconsistent state as usages update one by one. + +### Update + +[Resource.update](/rest/api/resource#update) and +[Resource.partialUpdate](/rest/api/resource#partialupdate) merge the response +into the one copy of that entity. Every consumer of that [pk](/rest/api/Entity#pk) +updates together. [Read more about defining other update endpoints](/rest/guides/side-effects). + +Close an issue. Data Client updates the list and the detail together. +Typical independent caches close the detail and leave the list open. + + + +### Create + +Created entities are immediately available. They are added to existing +[Collections](/rest/api/Collection) with +[.push](/rest/api/RestEndpoint#push), +[.unshift](/rest/api/RestEndpoint#unshift), or +[.assign](/rest/api/RestEndpoint#assign). + +Open an issue. Data Client adds it to the list and the newest detail. +Typical independent caches show it in detail while the list still misses it. + + + +### Delete + +[schema.Invalidate](/rest/api/Invalidate) removes the entity. +[Resource.delete](/rest/api/resource#delete) provides such an endpoint. + +Delete an issue. Data Client removes it from the list and the detail together. +Typical independent caches clear the detail and leave a ghost in the list. + + + +### Rollback + +Optimistic updates apply as that same snapshot. If the network fails, they +roll back as that snapshot. + +Close an issue. Data Client flips both views, then rolls both back on the 500. +Typical independent caches roll the detail back and leave the list closed. + + + +### Side effects + +When a mutation changes more than one resource, include every changed entity +in the response. That is one commit. [Invalidating](../api/Controller.md#expireAll) +and refetching the others can fail partway — a flash of torn state. + +[See mutation side-effects](/rest/guides/side-effects) for the full pattern. + +Buy DOGE. Data Client records the trade and the new balance in one commit. +Typical independent caches append the trade and leave the balance stale. + + + +## Consistency + +A write takes the store from one valid state to another. Invariants hold: +one copy of each entity, relationships join, invalid data is rejected. +That prevents *data tearing* — the same issue showing two different values. + +### Identity + +[Entity.pk()](/rest/api/Entity#pk) is the unique index. The same issue from +[getList](/rest/api/resource#getlist) and [get](/rest/api/resource#get) is the +**same object** — the same value, wherever it is embedded. + +Select an issue, then close it. Data Client `getList` and `get` stay locked +together. Typical independent caches keep two copies that drift. + + + +### Collections + +When [Collection.argsKey](/rest/api/Collection#argskey) and +[Collection.nestKey](/rest/api/Collection#nestkey) return the same shape, a nested +list and a top-level list are the **same array**. + +Close an issue. Data Client updates the repo page and the issues tab together. +Typical independent caches update one list and leave the other open. + + + +### Query + +[Query](/rest/api/Query) derived values stay consistent for the same reason — +they read the entity table, not a copy. + +Close issues. Data Client drops the open count immediately. Typical +independent caches keep a stale count. + + + +### Validation + +[Entity.validate()](./validation.md) is the check constraint. Invalid responses +are not committed. + +Switch between payloads. Data Client rejects invalid articles and keeps the +last good commit. Typical independent caches render the malformed fields. + + + +### Transports + +The same entity is the same value whether it arrived from fetch, initial +load, [Controller.set()](../api/Controller.md#set), or a +[websocket](./managers.md#data-stream). + +Click **Alice closed this**. Data Client updates the list and the detail. +Typical independent caches update a local detail copy and leave the list behind. + + + +## Isolation + +Concurrent work leaves the store as if it ran in sequence. A slower +response cannot confuse a newer local edit. + +### Fetch order + +Overlapping fetches complete in any order. Reactive Data Client pairs each +[optimistic update](/rest/guides/optimistic-updates) with its own request and +commits in [fetchedAt](/docs/api/Snapshot#fetchedat) order. A late response cannot +clobber a newer commit. + +```mermaid +sequenceDiagram + autonumber + participant Client + participant Server + Client->>+Server: Increment from 0 + Client->>+Server: Increment from 1 + Server->>-Client: Response: 2 + Server->>-Client: Response: 1 +``` + +With other libraries this would show 0, then 2, then 1. Reactive Data Client +keeps 0, 1, 2. + +Click increment several times quickly. + + + +[Optimistic updates](/rest/guides/optimistic-updates) amplify these races; +Reactive Data Client handles them automatically. + +### Snapshots + +All hooks in one render read the same snapshot, so the tree never paints mixed +old and new values. + +Close an issue. Data Client only paints matching `list`/`query` pairs. Typical +independent caches record a mixed-version paint. + + + +## Durability + +Once work is committed, it stays committed through a crash or a closed +tab. Storing in memory is not enough — mutations must reach an async API. +Later retrievals reflect those updates. + +### REST + +`ctrl.fetch` is the commit path. Saving as you go (a close, an inline +edit) commits to the server. Use a form when the friction is the point — +publish, purchase. + +Close some issues, type a draft comment, then simulate a crash. Data Client +refetches the closes from the server. Typical independent caches lose the +closes. Both lose the draft. + + + +In-flight optimistic updates are not the durable commit — the `fetch` is. + +### IndexedDB + +A [persist Manager](./managers.md#persistence) can replicate confirmed state to +[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) +for offline reloads. Restore it with +[DataProvider's initialState](../api/DataProvider.md#initialState). Drop +in-flight optimistic updates — they are not cloneable, and they are not the ack. + +:::info[Reactivity] + +ACID makes writes trustworthy. [useLive()](../api/useLive.md), +[polling](/rest/api/Endpoint#pollfrequency), and +[push](./managers.md#data-stream) keep the UI a live function of the store. +Reactivity is how you watch the durable store; it is not a substitute for +reaching it. + +::: diff --git a/docs/core/concepts/atomic-mutations.md b/docs/core/concepts/atomic-mutations.md deleted file mode 100644 index a2b3b26c22f0..000000000000 --- a/docs/core/concepts/atomic-mutations.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: '⚛ Atomic Mutations: Safe, high performance async mutations' -sidebar_label: Atomic Mutations ---- - - - - - -# Safety beyond types - -When a user causes mutations like creating, updating, or deleting resources, it's important -to have those changed be reflected in the application. A simple publish cache -that has no underlying knowledge of the data structures would require a refetch of any endpoints -that are changed. This would reduce performance and put extra burden on the backend. - -However, like many other cases, a normalized cache - one with underlying knowledge of the relationships -between resources - is capable of keeping all data consistent and fresh without -any refetches. - -## Update - -Reactive Data Client uses your schema definitions to understand how to normalize response data into -an `entity table` and `result table`. Of course, this means that there is only ever one copy -of a given `entity`. Aside from providing consistency when using different response endpoints, -this means that by providing an accurate schema definition, Reactive Data Client can automatically keep -all data uses consistent and fresh. The default update endpoints [Resource.update](/rest/api/resource#update) and -[Resource.partialUpdate](/rest/api/resource#partialupdate) both do this automatically. [Read more about defining other -update endpoints](/rest/guides/side-effects) - -## Delete - -Reactive Data Client automatically deletes entity entries [schema.Invalidate](/rest/api/Invalidate) is used. -[Resource.delete](/rest/api/resource#delete) -provides such an endpoint. - -## Create - -Created entities are immediately available. They can also be added to existing [Collections](/rest/api/Collection) -with [.push](/rest/api/RestEndpoint#push), [.unshift](/rest/api/RestEndpoint#unshift), or [.assign](/rest/api/RestEndpoint#assign). \ No newline at end of file diff --git a/docs/core/concepts/overview.md b/docs/core/concepts/overview.md index 7d00b1246f5b..2eb18928f6fe 100644 --- a/docs/core/concepts/overview.md +++ b/docs/core/concepts/overview.md @@ -26,7 +26,7 @@ import Link from '@docusaurus/Link'; - Expiry Status + Expiry Status Fresh Data can always be used and needs no updates. @@ -41,7 +41,7 @@ import Link from '@docusaurus/Link'; - Error Policy + Error Policy Soft Transient errors that should not invalidate existing data. diff --git a/docs/core/getting-started/mutations.md b/docs/core/getting-started/mutations.md index 44f881adfc18..301db56a06b8 100644 --- a/docs/core/getting-started/mutations.md +++ b/docs/core/getting-started/mutations.md @@ -18,7 +18,7 @@ import VoteDemo from '../shared/\_VoteDemo.mdx'; # Data mutations -Using our [Create, Update, and Delete](/docs/concepts/atomic-mutations) endpoints with +Using our [Create, Update, and Delete](/docs/concepts/acid) endpoints with [Controller.fetch()](../api/Controller.md#fetch) reactively updates _all_ appropriate components atomically (at the same time). [useController()](../api/useController.md) gives components access to this global supercharged [setState()](https://react.dev/reference/react/useState#setstate). diff --git a/docs/core/shared/_acidCollections.mdx b/docs/core/shared/_acidCollections.mdx new file mode 100644 index 000000000000..5b0e6e14eaae --- /dev/null +++ b/docs/core/shared/_acidCollections.mdx @@ -0,0 +1,173 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidCollectionFixtures, + getAcidCollectionData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" {12-15} +import { Entity, RestEndpoint, Collection } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} + +export const repoIssues = new Collection([Issue], { + argsKey: ({ repoId }: { repoId?: string }) => ({ repoId }), + nestKey: (parent: { id: string }) => ({ repoId: parent.id }), +}); + +export const getIssues = new RestEndpoint({ + path: '/issues', + searchParams: {} as { repoId?: string }, + schema: repoIssues, +}); + +export const updateIssue = new RestEndpoint({ + path: '/issues/:id', + method: 'PATCH', + schema: Issue, + getOptimisticResponse(snap, { id }, body) { + const cur = snap.get(Issue, { id }); + if (!cur) throw snap.abort; + return { ...cur, ...body }; + }, +}); +``` + +```ts title="RepoResource" collapsed +import { Entity, RestEndpoint } from '@data-client/rest'; +import { Issue, repoIssues } from './IssueResource'; + +export class Repo extends Entity { + id = ''; + name = ''; + issues: Issue[] = []; + + static key = 'Repo'; + static schema = { + issues: repoIssues, + }; +} + +export const getRepo = new RestEndpoint({ + path: '/repos/:id', + schema: Repo, +}); +``` + +```ts title="TypicalCache" collapsed +export function useTypicalCollections(seed) { + const [repoIssues, setRepoIssues] = React.useState(() => + seed.map(issue => ({ ...issue })), + ); + const [tabIssues] = React.useState(() => + seed.map(issue => ({ ...issue })), + ); + const update = (id, body) => + setRepoIssues(current => + current.map(issue => (issue.id === id ? { ...issue, ...body } : issue)), + ); + const torn = repoIssues.some( + (issue, i) => issue.state !== tabIssues[i].state, + ); + return { repoIssues, tabIssues, update, torn }; +} +``` + +```tsx title="IssuePage" collapsed +import { useController, useSuspense } from '@data-client/react'; +import { getIssues, updateIssue } from './IssueResource'; +import { getRepo } from './RepoResource'; +import { useTypicalCollections } from './TypicalCache'; + +function IssuePage() { + const ctrl = useController(); + const repo = useSuspense(getRepo, { id: '1' }); + const issues = useSuspense(getIssues, { repoId: '1' }); + const typical = useTypicalCollections(issues); + const handleToggle = issue => { + const state = issue.state === 'open' ? 'closed' : 'open'; + ctrl.fetch(updateIssue, { id: issue.id }, { state }); + typical.update(issue.id, { state }); + }; + return ( +
+ {repo.issues.map(issue => ( + + + + ))} + + +
+
+ Repo page + {repo.issues.map(issue => ( + + ))} +
+
+ Issues tab + {issues.map(issue => ( + + ))} +
+
+
+ +
+
+ Repo page + {typical.repoIssues.map(issue => ( + + ))} +
+
+ Issues tab + {typical.tabIssues.map(issue => ( + + ))} +
+
+
+
+
+ ); +} +render(); +``` + +
diff --git a/docs/core/shared/_acidCreate.mdx b/docs/core/shared/_acidCreate.mdx new file mode 100644 index 000000000000..5f51b5335b5b --- /dev/null +++ b/docs/core/shared/_acidCreate.mdx @@ -0,0 +1,124 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidIssueFixtures, + getAcidIssueData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" collapsed +import { Entity, resource } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} +export const IssueResource = resource({ + path: '/issues/:id', + searchParams: {} as { repoId?: string } | undefined, + schema: Issue, + optimistic: true, +}); +``` + +```ts title="TypicalCache" collapsed +export function useTypicalCreate(seed) { + const [list] = React.useState(() => seed.map(issue => ({ ...issue }))); + const [orphan, setOrphan] = React.useState(null); + const create = title => + setOrphan({ id: 'local', title, state: 'open' }); + return { list, orphan, create, torn: orphan != null }; +} +``` + +```tsx title="IssuePage" {14} +import { useController, useSuspense } from '@data-client/react'; +import { IssueResource } from './IssueResource'; +import { useTypicalCreate } from './TypicalCache'; + +function IssuePage() { + const ctrl = useController(); + const issues = useSuspense(IssueResource.getList, { repoId: '1' }); + const typical = useTypicalCreate(issues); + const newest = issues[issues.length - 1]; + const issue = useSuspense(IssueResource.get, { id: newest.id }); + const handleKeyDown = e => { + if (e.key === 'Enter' && e.currentTarget.value.trim()) { + const title = e.currentTarget.value; + ctrl.fetch(IssueResource.getList.push, { repoId: '1', title }); + typical.create(title); + e.currentTarget.value = ''; + } + }; + return ( +
+
+ +
+ + +
+
+ List + {issues.map(item => ( + + ))} +
+
+ Newest +
{issue.title}
+ +
+
+
+ +
+
+ List + {typical.list.map(item => ( + + ))} +
+
+ Newest + {typical.orphan ? + <> +
{typical.orphan.title}
+ + + : <> +
{newest.title}
+ + + } +
+
+
+
+
+ ); +} +render(); +``` + +
diff --git a/docs/core/shared/_acidDelete.mdx b/docs/core/shared/_acidDelete.mdx new file mode 100644 index 000000000000..e02beed30814 --- /dev/null +++ b/docs/core/shared/_acidDelete.mdx @@ -0,0 +1,120 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidIssueFixtures, + getAcidIssueData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" collapsed +import { Entity, resource } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} +export const IssueResource = resource({ + path: '/issues/:id', + searchParams: {} as { repoId?: string } | undefined, + schema: Issue, + optimistic: true, +}); +``` + +```ts title="TypicalCache" collapsed +export function useTypicalDelete(seed) { + const [list] = React.useState(() => seed.map(issue => ({ ...issue }))); + const [deleted, setDeleted] = React.useState({}); + const remove = id => setDeleted(current => ({ ...current, [id]: true })); + return { list, deleted, remove }; +} +``` + +```tsx title="IssuePage" {16} +import { useController, useSuspense } from '@data-client/react'; +import { IssueResource } from './IssueResource'; +import { useTypicalDelete } from './TypicalCache'; + +function IssuePage() { + const ctrl = useController(); + const issues = useSuspense(IssueResource.getList, { repoId: '1' }); + const typical = useTypicalDelete(issues); + const [id, setId] = React.useState(issues[0]?.id); + const selected = issues.find(item => item.id === id) ?? issues[0]; + const issue = useSuspense( + IssueResource.get, + selected ? { id: selected.id } : null, + ); + const handleDelete = () => { + ctrl.fetch(IssueResource.delete, { id: selected.id }); + typical.remove(selected.id); + }; + return ( + + +
+
+ List + {issues.map(item => ( + setId(item.id)} + /> + ))} +
+
+ Detail + {issue ? +
+ {issue.title} + +
+ : No issues} +
+
+
+ 0} + > +
+
+ List + {typical.list.map(item => ( + setId(item.id)} + /> + ))} +
+
+ Detail + {typical.deleted[id] ? + Deleted + :
+ {typical.list.find(item => item.id === id)?.title} + +
+ } +
+
+
+
+ ); +} +render(); +``` + +
diff --git a/docs/core/shared/_acidIdentity.mdx b/docs/core/shared/_acidIdentity.mdx new file mode 100644 index 000000000000..25ae35dd8161 --- /dev/null +++ b/docs/core/shared/_acidIdentity.mdx @@ -0,0 +1,116 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidIssueFixtures, + getAcidIssueData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" collapsed +import { Entity, resource } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} +export const IssueResource = resource({ + path: '/issues/:id', + searchParams: {} as { repoId?: string } | undefined, + schema: Issue, + optimistic: true, +}); +``` + +```ts title="TypicalCache" collapsed +export function useTypicalIdentity(seed) { + const [list] = React.useState(() => seed.map(issue => ({ ...issue }))); + const [details, setDetails] = React.useState(() => + Object.fromEntries(seed.map(issue => [issue.id, { ...issue }])), + ); + const view = id => details[id]; + const update = (id, body) => + setDetails(current => ({ + ...current, + [id]: { ...current[id], ...body }, + })); + const torn = id => list.find(issue => issue.id === id)?.state !== view(id)?.state; + return { list, view, update, torn }; +} +``` + +```tsx title="IssuePage" {7,10,11} +import { useController, useSuspense } from '@data-client/react'; +import { IssueResource } from './IssueResource'; +import { useTypicalIdentity } from './TypicalCache'; + +function IssuePage() { + const ctrl = useController(); + const issues = useSuspense(IssueResource.getList, { repoId: '1' }); + const typical = useTypicalIdentity(issues); + const [id, setId] = React.useState(issues[0].id); + const issue = useSuspense(IssueResource.get, { id }); + const fromList = issues.find(item => item.id === id); + const handleToggle = () => { + const state = issue.state === 'open' ? 'closed' : 'open'; + ctrl.fetch(IssueResource.partialUpdate, { id }, { state }); + typical.update(id, { state }); + }; + return ( +
+ {issues.map(item => ( + setId(item.id)} + /> + ))} +
+ +
+ + +
+
+ getList + +
+
+ get + +
+
+
+ +
+
+ getList + item.id === id).state} + /> +
+
+ get + +
+
+
+
+
+ ); +} +render(); +``` + +
diff --git a/docs/core/shared/_acidQuery.mdx b/docs/core/shared/_acidQuery.mdx new file mode 100644 index 000000000000..c941df0e8168 --- /dev/null +++ b/docs/core/shared/_acidQuery.mdx @@ -0,0 +1,112 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidIssueFixtures, + getAcidIssueData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" {18-21} +import { Entity, resource, Query } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} +export const IssueResource = resource({ + path: '/issues/:id', + searchParams: {} as { repoId?: string } | undefined, + schema: Issue, + optimistic: true, +}); + +export const openCount = new Query( + IssueResource.getList.schema, + entries => entries.filter(issue => issue.state === 'open').length, +); +``` + +```ts title="TypicalCache" collapsed +export function useTypicalQuery(seed) { + const [list, setList] = React.useState(() => + seed.map(issue => ({ ...issue })), + ); + const [open] = React.useState( + () => seed.filter(issue => issue.state === 'open').length, + ); + const update = (id, body) => + setList(current => + current.map(issue => (issue.id === id ? { ...issue, ...body } : issue)), + ); + const liveOpen = list.filter(issue => issue.state === 'open').length; + return { list, open, update, torn: open !== liveOpen }; +} +``` + +```tsx title="IssuePage" collapsed +import { useController, useQuery, useSuspense } from '@data-client/react'; +import { openCount, IssueResource } from './IssueResource'; +import { useTypicalQuery } from './TypicalCache'; + +function IssuePage() { + const ctrl = useController(); + const issues = useSuspense(IssueResource.getList, { repoId: '1' }); + const open = useQuery(openCount, { repoId: '1' }); + const typical = useTypicalQuery(issues); + const handleToggle = issue => { + const state = issue.state === 'open' ? 'closed' : 'open'; + ctrl.fetch(IssueResource.partialUpdate, { id: issue.id }, { state }); + typical.update(issue.id, { state }); + }; + return ( +
+ {issues.map(issue => ( + + + + ))} + + +
+ Open issues + + + +
+
+ +
+ Open issues + + + {typical.open} + + +
+
+
+
+ ); +} +render(); +``` + +
diff --git a/docs/core/shared/_acidRest.mdx b/docs/core/shared/_acidRest.mdx new file mode 100644 index 000000000000..3e4d05391cde --- /dev/null +++ b/docs/core/shared/_acidRest.mdx @@ -0,0 +1,133 @@ +import HooksPlayground from '@site/src/components/HooksPlayground'; +import { + acidIssueFixtures, + getAcidIssueData, +} from '@site/src/fixtures/acid'; + + + +```ts title="IssueResource" collapsed +import { Entity, resource } from '@data-client/rest'; + +export class Issue extends Entity { + id = ''; + repoId = ''; + title = ''; + state: 'open' | 'closed' = 'open'; + + static key = 'Issue'; +} +export const IssueResource = resource({ + path: '/issues/:id', + searchParams: {} as { repoId?: string } | undefined, + schema: Issue, + optimistic: true, +}); +``` + +```ts title="TypicalCache" collapsed +const SEED = [ + { id: '3', repoId: '1', title: 'Rate limit the API', state: 'closed' }, + { id: '1', repoId: '1', title: 'Fix login timeout', state: 'open' }, + { id: '2', repoId: '1', title: 'Document ACID guarantees', state: 'open' }, +]; + +export function useTypicalSession() { + const [list, setList] = React.useState(() => + SEED.map(issue => ({ ...issue })), + ); + const update = (id, body) => + setList(current => + current.map(issue => (issue.id === id ? { ...issue, ...body } : issue)), + ); + return { list, update }; +} +``` + +```tsx title="Session" collapsed +import { useController, useSuspense } from '@data-client/react'; +import { IssueResource } from './IssueResource'; +import { useTypicalSession } from './TypicalCache'; + +export default function Session({ draft, setDraft }) { + const ctrl = useController(); + const issues = useSuspense(IssueResource.getList, { repoId: '1' }); + const typical = useTypicalSession(); + const handleToggle = issue => { + const state = issue.state === 'open' ? 'closed' : 'open'; + ctrl.fetch(IssueResource.partialUpdate, { id: issue.id }, { state }); + typical.update(issue.id, { state }); + }; + return ( + + + ctrl.fetch — survives crash + {issues.map(issue => ( + + + + ))} +