clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/184-task-notice-migration
lofi / website / src / components / SliceMerge.tsx
SliceMerge.tsx 3.8K
1import type { ReactNode } from "react";2import { useState } from "react";34// The shared-store story you can run: an empty store (where writes would5// hang), a first app provisioning its slice, a second app merging in beside6// it, and a drifted app being refused. Mirrors the real classifier states.78type Phase = "empty" | "taskapp" | "merged";910export default function SliceMerge(): ReactNode {11 const [phase, setPhase] = useState<Phase>("empty");12 const [driftTried, setDriftTried] = useState(false);1314 const status = phase === "empty"15 ? {16 code: "no_schema",17 note: "writes would hang; provision before syncing",18 }19 : phase === "taskapp"20 ? { code: "ok", note: "head h1 · taskapp's slice enforced" }21 : driftTried22 ? {23 code: "schema_drift",24 note: "taskapp__tasks differs; surfaced, never repaired",25 }26 : {27 code: "ok",28 note: "head h2 · both slices enforced, one migration apart",29 };3031 return (32 <div className="sm" data-phase={phase}>33 <div className="sm-bar">34 <span className="sm-status" data-code={status.code}>35 <i />36 {status.code}37 </span>38 <span className="sm-note">{status.note}</span>39 </div>4041 <div className="sm-shelf" aria-hidden="true">42 {phase === "empty"43 ? <span className="sm-empty">one store · nothing deployed</span>44 : (45 <>46 <div className="sm-slice sm-slice--task">47 <span className="sm-ns">taskapp__</span>48 <span49 className={`sm-table ${driftTried ? "is-contested" : ""}`}50 >51 tasks52 </span>53 <span className="sm-table">projects</span>54 <span className="sm-seal">55 {driftTried56 ? "the store's copy no longer matches the declaration"57 : phase === "merged"58 ? "byte-identical through the merge"59 : "created by taskapp's provisioning"}60 </span>61 </div>62 {phase === "merged" && (63 <div className="sm-slice sm-slice--notes">64 <span className="sm-ns">notesapp__</span>65 <span className="sm-table">notes</span>66 <span className="sm-seal">67 added via createTables migration68 </span>69 </div>70 )}71 </>72 )}73 </div>7475 <div className="sm-actions">76 {phase === "empty" && (77 <button78 type="button"79 className="np-btn np-btn--go"80 onClick={() => setPhase("taskapp")}81 >82 taskapp provisions its slice83 </button>84 )}85 {phase === "taskapp" && (86 <button87 type="button"88 className="np-btn np-btn--go"89 onClick={() => setPhase("merged")}90 >91 notesapp joins the occupied store92 </button>93 )}94 {phase === "merged" && !driftTried && (95 <button96 type="button"97 className="np-btn"98 onClick={() => setDriftTried(true)}99 >100 a drifted taskapp variant tries101 </button>102 )}103 {driftTried && (104 <button105 type="button"106 className="np-btn np-btn--ghost"107 onClick={() => setDriftTried(false)}108 >109 back away slowly110 </button>111 )}112 {phase !== "empty" && !driftTried && (113 <button114 type="button"115 className="np-btn np-btn--ghost"116 onClick={() => {117 setPhase("empty");118 setDriftTried(false);119 }}120 >121 reset122 </button>123 )}124 </div>125 </div>126 );127}