[{"data":1,"prerenderedAt":1174},["ShallowReactive",2],{"cycle-doc-design-c134-plan.md":3},{"cycle":4,"project":7,"path":10,"title":11,"toc":12,"ast":22},{"id":5,"title":6},"c134","Control Plane Web App",{"id":8,"label":9},"design","Design","plan.md","c134 plan: Control Plane Web App (draft, before kick-off)",[13,14,15,16,17,18,19,20,21],"Why","What exists","The shape","Design's deliverables","Rules","Decisions (defaults)","Phases","Clarifying questions","Patterns to consider",{"type":23,"children":24},"root",[25,62,68,73,78,531,536,548,574,579,690,695,748,753,957,962,969,979,985,998,1004,1016,1021,1027,1032,1038,1043,1049,1054,1059,1099,1104],{"type":26,"tag":27,"props":28,"children":29},"element","p",{},[30,37,39,44,46,51,53,60],{"type":26,"tag":31,"props":32,"children":33},"strong",{},[34],{"type":35,"value":36},"text","Gate:",{"type":35,"value":38}," Tyler says Kamal is free to start the data model, and rules where the code lives; Mark's word. Phase 0 (the inventory of both apps) can run before the gate: kicked off 2026-11-09 with Phase 0 in its own session.\n",{"type":26,"tag":31,"props":40,"children":41},{},[42],{"type":35,"value":43},"Reader:",{"type":35,"value":45}," Mark; Tyler and Kamal for the contract and the port plan; the executor who mocks the shell, auth and install flows.\n",{"type":26,"tag":31,"props":47,"children":48},{},[49],{"type":35,"value":50},"Session:",{"type":35,"value":52}," ",{"type":26,"tag":54,"props":55,"children":57},"code",{"className":56},[],[58],{"type":35,"value":59},"1b21a3ce",{"type":35,"value":61},". Rob: SaaS hardening doesn't block self-hosted adoption. Mark Peterson owns the local install experience and docs.",{"type":26,"tag":63,"props":64,"children":66},"h2",{"id":65},"why",[67],{"type":35,"value":13},{"type":26,"tag":27,"props":69,"children":70},{},[71],{"type":35,"value":72},"The dashboard leaves qcontrol and becomes the control plane's UI: the thing you connect endpoints to, offered as SaaS and as a Docker image a company runs itself. Mark's prompt says app.qpoint.io already models it: the org and context frame, auth, the Pulse-backed data patterns, the wizards, alerting. Tyler's plan keeps Pulse (Go) and puts a new data model in it; Firebase goes because it can't be self-hosted. The pages are the qdash UI we have been building since c92. Design's part is the seam: which shell wins, what the API contract is, how people sign in without Firebase, how the thing installs, and the plan for the port.",{"type":26,"tag":63,"props":74,"children":76},{"id":75},"what-exists",[77],{"type":35,"value":14},{"type":26,"tag":79,"props":80,"children":81},"ul",{},[82,223,455,496,506],{"type":26,"tag":83,"props":84,"children":85},"li",{},[86,91,93,99,101,107,109,115,116,122,123,129,130,136,138,144,145,151,153,159,161,167,169,175,177,183,185,191,193,199,200,206,207,213,215,221],{"type":26,"tag":31,"props":87,"children":88},{},[89],{"type":35,"value":90},"app.qpoint.io",{"type":35,"value":92}," (",{"type":26,"tag":54,"props":94,"children":96},{"className":95},[],[97],{"type":35,"value":98},"..\u002F..\u002F..\u002Fapp.qpoint.io",{"type":35,"value":100},"): Nuxt 4 SPA, Pinia stores (",{"type":26,"tag":54,"props":102,"children":104},{"className":103},[],[105],{"type":35,"value":106},"auth",{"type":35,"value":108},", ",{"type":26,"tag":54,"props":110,"children":112},{"className":111},[],[113],{"type":35,"value":114},"flash",{"type":35,"value":108},{"type":26,"tag":54,"props":117,"children":119},{"className":118},[],[120],{"type":35,"value":121},"pulse",{"type":35,"value":108},{"type":26,"tag":54,"props":124,"children":126},{"className":125},[],[127],{"type":35,"value":128},"query-string",{"type":35,"value":108},{"type":26,"tag":54,"props":131,"children":133},{"className":132},[],[134],{"type":35,"value":135},"top-nav",{"type":35,"value":137},"), layouts ",{"type":26,"tag":54,"props":139,"children":141},{"className":140},[],[142],{"type":35,"value":143},"default \u002F global \u002F org \u002F session \u002F dev",{"type":35,"value":108},{"type":26,"tag":54,"props":146,"children":148},{"className":147},[],[149],{"type":35,"value":150},"auth.global.ts",{"type":35,"value":152}," and ",{"type":26,"tag":54,"props":154,"children":156},{"className":155},[],[157],{"type":35,"value":158},"context.global.ts",{"type":35,"value":160},", Firebase 11 (email, Google, GitHub, Microsoft, SSO), SAML routes (",{"type":26,"tag":54,"props":162,"children":164},{"className":163},[],[165],{"type":35,"value":166},"server\u002Froutes\u002Fsso\u002Fsaml\u002F",{"type":35,"value":168},"), the Pulse plugin (",{"type":26,"tag":54,"props":170,"children":172},{"className":171},[],[173],{"type":35,"value":174},"$pulse.fetch",{"type":35,"value":176},"), ",{"type":26,"tag":54,"props":178,"children":180},{"className":179},[],[181],{"type":35,"value":182},"useList",{"type":35,"value":184}," \u002F ",{"type":26,"tag":54,"props":186,"children":188},{"className":187},[],[189],{"type":35,"value":190},"useEntity",{"type":35,"value":192}," over Firestore, org-scoped composables, alerting rules and integrations with adapters, wizards (",{"type":26,"tag":54,"props":194,"children":196},{"className":195},[],[197],{"type":35,"value":198},"InstallQtap",{"type":35,"value":108},{"type":26,"tag":54,"props":201,"children":203},{"className":202},[],[204],{"type":35,"value":205},"DeployQproxy",{"type":35,"value":108},{"type":26,"tag":54,"props":208,"children":210},{"className":209},[],[211],{"type":35,"value":212},"ConnectApp",{"type":35,"value":214},"), a ",{"type":26,"tag":54,"props":216,"children":218},{"className":217},[],[219],{"type":35,"value":220},"dev\u002F",{"type":35,"value":222}," showcase. Last commit 2026-10-27. Memory note (2026-10-02): the deployed site was considered stale and unused; it is the model, not the base.",{"type":26,"tag":83,"props":224,"children":225},{},[226,231,232,238,239,245,246,252,254,260,261,267,268,274,275,281,282,288,289,295,296,302,303,309,310,316,317,323,324,330,331,337,338,344,345,351,352,358,359,365,366,372,373,379,380,386,387,393,394,400,402,408,410,416,417,423,424,430,431,437,439,445,447,453],{"type":26,"tag":31,"props":227,"children":228},{},[229],{"type":35,"value":230},"qdash UI",{"type":35,"value":92},{"type":26,"tag":54,"props":233,"children":235},{"className":234},[],[236],{"type":35,"value":237},"..\u002F..\u002F..\u002Fqcontrol\u002Fcrates\u002Fqdash\u002Fui",{"type":35,"value":108},{"type":26,"tag":54,"props":240,"children":242},{"className":241},[],[243],{"type":35,"value":244},"feat\u002Fnew-pages",{"type":35,"value":108},{"type":26,"tag":54,"props":247,"children":249},{"className":248},[],[250],{"type":35,"value":251},":3030",{"type":35,"value":253},"): the pages (",{"type":26,"tag":54,"props":255,"children":257},{"className":256},[],[258],{"type":35,"value":259},"overview",{"type":35,"value":108},{"type":26,"tag":54,"props":262,"children":264},{"className":263},[],[265],{"type":35,"value":266},"inventory\u002F*",{"type":35,"value":108},{"type":26,"tag":54,"props":269,"children":271},{"className":270},[],[272],{"type":35,"value":273},"activity",{"type":35,"value":108},{"type":26,"tag":54,"props":276,"children":278},{"className":277},[],[279],{"type":35,"value":280},"live",{"type":35,"value":108},{"type":26,"tag":54,"props":283,"children":285},{"className":284},[],[286],{"type":35,"value":287},"organization\u002F*",{"type":35,"value":108},{"type":26,"tag":54,"props":290,"children":292},{"className":291},[],[293],{"type":35,"value":294},"policies",{"type":35,"value":108},{"type":26,"tag":54,"props":297,"children":299},{"className":298},[],[300],{"type":35,"value":301},"findings",{"type":35,"value":108},{"type":26,"tag":54,"props":304,"children":306},{"className":305},[],[307],{"type":35,"value":308},"compliance",{"type":35,"value":108},{"type":26,"tag":54,"props":311,"children":313},{"className":312},[],[314],{"type":35,"value":315},"framework",{"type":35,"value":108},{"type":26,"tag":54,"props":318,"children":320},{"className":319},[],[321],{"type":35,"value":322},"integrations",{"type":35,"value":108},{"type":26,"tag":54,"props":325,"children":327},{"className":326},[],[328],{"type":35,"value":329},"cost",{"type":35,"value":108},{"type":26,"tag":54,"props":332,"children":334},{"className":333},[],[335],{"type":35,"value":336},"alerts",{"type":35,"value":108},{"type":26,"tag":54,"props":339,"children":341},{"className":340},[],[342],{"type":35,"value":343},"campaigns",{"type":35,"value":108},{"type":26,"tag":54,"props":346,"children":348},{"className":347},[],[349],{"type":35,"value":350},"dlp",{"type":35,"value":108},{"type":26,"tag":54,"props":353,"children":355},{"className":354},[],[356],{"type":35,"value":357},"identity",{"type":35,"value":108},{"type":26,"tag":54,"props":360,"children":362},{"className":361},[],[363],{"type":35,"value":364},"performance",{"type":35,"value":108},{"type":26,"tag":54,"props":367,"children":369},{"className":368},[],[370],{"type":35,"value":371},"posture",{"type":35,"value":108},{"type":26,"tag":54,"props":374,"children":376},{"className":375},[],[377],{"type":35,"value":378},"security",{"type":35,"value":108},{"type":26,"tag":54,"props":381,"children":383},{"className":382},[],[384],{"type":35,"value":385},"reports",{"type":35,"value":108},{"type":26,"tag":54,"props":388,"children":390},{"className":389},[],[391],{"type":35,"value":392},"settings",{"type":35,"value":108},{"type":26,"tag":54,"props":395,"children":397},{"className":396},[],[398],{"type":35,"value":399},"account",{"type":35,"value":401},"), the shell with the global filter and the ",{"type":26,"tag":54,"props":403,"children":405},{"className":404},[],[406],{"type":35,"value":407},"when:",{"type":35,"value":409}," pill, ",{"type":26,"tag":54,"props":411,"children":413},{"className":412},[],[414],{"type":35,"value":415},"composables\u002Fapi\u002Ftransport.ts",{"type":35,"value":92},{"type":26,"tag":54,"props":418,"children":420},{"className":419},[],[421],{"type":35,"value":422},"http()",{"type":35,"value":108},{"type":26,"tag":54,"props":425,"children":427},{"className":426},[],[428],{"type":35,"value":429},"local()",{"type":35,"value":176},{"type":26,"tag":54,"props":432,"children":434},{"className":433},[],[435],{"type":35,"value":436},"types\u002Fapi\u002F*",{"type":35,"value":438}," written from the backend handlers (R13), ",{"type":26,"tag":54,"props":440,"children":442},{"className":441},[],[443],{"type":35,"value":444},"lib\u002F*-rollups.ts",{"type":35,"value":446}," with vitest, ",{"type":26,"tag":54,"props":448,"children":450},{"className":449},[],[451],{"type":35,"value":452},"LAYER-CANDIDATES.md",{"type":35,"value":454},", c108's honest states, c113's baked worlds for the demo.",{"type":26,"tag":83,"props":456,"children":457},{},[458,463,464,470,472,478,480,486,488,494],{"type":26,"tag":31,"props":459,"children":460},{},[461],{"type":35,"value":462},"The qdash backend",{"type":35,"value":92},{"type":26,"tag":54,"props":465,"children":467},{"className":466},[],[468],{"type":35,"value":469},"crates\u002Fqdash\u002Fsrc",{"type":35,"value":471},"): 38 ",{"type":26,"tag":54,"props":473,"children":475},{"className":474},[],[476],{"type":35,"value":477},"\u002Fapi\u002F*",{"type":35,"value":479}," routes over SQLite, plus ",{"type":26,"tag":54,"props":481,"children":483},{"className":482},[],[484],{"type":35,"value":485},"\u002Fapi\u002Fevents\u002Fstream",{"type":35,"value":487},". The seed (",{"type":26,"tag":54,"props":489,"children":491},{"className":490},[],[492],{"type":35,"value":493},"scripts\u002Fqdash-seed",{"type":35,"value":495},") generates the Meridian world.",{"type":26,"tag":83,"props":497,"children":498},{},[499,504],{"type":26,"tag":31,"props":500,"children":501},{},[502],{"type":35,"value":503},"Pulse",{"type":35,"value":505},": not in this tree. Tyler: handcrafted filtering and views, \"the JavaScript filter\" on the backend side; Kamal strips the data model and puts the new one in.",{"type":26,"tag":83,"props":507,"children":508},{},[509,514,516,522,523,529],{"type":26,"tag":31,"props":510,"children":511},{},[512],{"type":35,"value":513},"Backend asks",{"type":35,"value":515}," already recorded: ",{"type":26,"tag":54,"props":517,"children":519},{"className":518},[],[520],{"type":35,"value":521},"from",{"type":35,"value":184},{"type":26,"tag":54,"props":524,"children":526},{"className":525},[],[527],{"type":35,"value":528},"to",{"type":35,"value":530}," on every page and history beyond 90 days (c128), snapshots, sensor version and health, seat prices (c105), staged state that should be served (bible R25 list).",{"type":26,"tag":63,"props":532,"children":534},{"id":533},"the-shape",[535],{"type":35,"value":15},{"type":26,"tag":537,"props":538,"children":542},"pre",{"className":539,"code":541,"language":35},[540],"language-text","┌ control plane (one Docker image) ─────────────────────────────────────┐\n│  UI (Nuxt, static)  ──▶  Go app (Pulse, new data model, API first)    │\n│                              │                │                       │\n│                          Postgres         ClickHouse                  │\n│                        (state, orgs,     (events, audit,              │\n│                         policies)         activity)                   │\n└───────────────────────────────────────────────────────────────────────┘\n        ▲ connect (OAuth-style, from the native app)      ▲ SSO \u002F OIDC \u002F local accounts\n  endpoints (qcontrol + native app)                   people (operators, viewers)\n",[543],{"type":26,"tag":54,"props":544,"children":546},{"__ignoreMap":545},"",[547],{"type":35,"value":541},{"type":26,"tag":27,"props":549,"children":550},{},[551,553,558,559,565,567,572],{"type":35,"value":552},"Two deployments, one build: ",{"type":26,"tag":31,"props":554,"children":555},{},[556],{"type":35,"value":557},"self-hosted",{"type":35,"value":92},{"type":26,"tag":54,"props":560,"children":562},{"className":561},[],[563],{"type":35,"value":564},"docker compose up",{"type":35,"value":566},", env for the two databases and secrets) and ",{"type":26,"tag":31,"props":568,"children":569},{},[570],{"type":35,"value":571},"SaaS",{"type":35,"value":573}," (we run the same image per customer).",{"type":26,"tag":63,"props":575,"children":577},{"id":576},"designs-deliverables",[578],{"type":35,"value":16},{"type":26,"tag":580,"props":581,"children":582},"ol",{},[583,597,607,637,647,664],{"type":26,"tag":83,"props":584,"children":585},{},[586,595],{"type":26,"tag":31,"props":587,"children":588},{},[589],{"type":26,"tag":54,"props":590,"children":592},{"className":591},[],[593],{"type":35,"value":594},"inventory.md",{"type":35,"value":596}," — both apps mapped: pages, shells, auth and context flows, data access, wizards; what moves, what is replaced, what is dropped.",{"type":26,"tag":83,"props":598,"children":599},{},[600,605],{"type":26,"tag":31,"props":601,"children":602},{},[603],{"type":35,"value":604},"The shell decision",{"type":35,"value":606}," — one mock of the qdash pages inside the chosen frame (D1), with sign-in, org switch and the global filter together.",{"type":26,"tag":83,"props":608,"children":609},{},[610,615,617,622,624,629,630,635],{"type":26,"tag":31,"props":611,"children":612},{},[613],{"type":35,"value":614},"The API contract",{"type":35,"value":616}," — ",{"type":26,"tag":54,"props":618,"children":620},{"className":619},[],[621],{"type":35,"value":436},{"type":35,"value":623}," and the 38 routes plus the backend asks, written as the contract Kamal implements; the window (",{"type":26,"tag":54,"props":625,"children":627},{"className":626},[],[628],{"type":35,"value":521},{"type":35,"value":184},{"type":26,"tag":54,"props":631,"children":633},{"className":632},[],[634],{"type":35,"value":528},{"type":35,"value":636},") on every list and rollup; auth and org scoping on every call. A document, reviewed with Tyler and Kamal.",{"type":26,"tag":83,"props":638,"children":639},{},[640,645],{"type":26,"tag":31,"props":641,"children":642},{},[643],{"type":35,"value":644},"Identity and access without Firebase",{"type":35,"value":646}," — sign-in, sign-up or invite, SSO (SAML exists; OIDC added), local accounts for self-hosted, roles (operator, viewer; c128's readers), mocked in the lab. Plus two things ruled on 2026-11-09: the standardised credential an endpoint's native app uses to connect to a plane at a base URL (c131 D13), and mechanic-access grants (c136): time-boxed, categorised access a partner gives Qpoint, held as plane objects.",{"type":26,"tag":83,"props":648,"children":649},{},[650,655,657,662],{"type":26,"tag":31,"props":651,"children":652},{},[653],{"type":35,"value":654},"The install experience",{"type":35,"value":656}," — first run: ",{"type":26,"tag":54,"props":658,"children":660},{"className":659},[],[661],{"type":35,"value":564},{"type":35,"value":663},", the first admin, connect the first endpoint (the native app's Connect is the other half), the demo world as an option. On c125's wizard kit. With Mark Peterson's install docs.",{"type":26,"tag":83,"props":665,"children":666},{},[667,672,674,680,682,688],{"type":26,"tag":31,"props":668,"children":669},{},[670],{"type":35,"value":671},"The port plan",{"type":35,"value":673}," — repo, build, how the qdash UI moves out of qcontrol, how ",{"type":26,"tag":54,"props":675,"children":677},{"className":676},[],[678],{"type":35,"value":679},"dist\u002F",{"type":35,"value":681}," and the Docker image are produced, CI, the cut-over, and what happens to ",{"type":26,"tag":54,"props":683,"children":685},{"className":684},[],[686],{"type":35,"value":687},"demo.qpoint.io",{"type":35,"value":689}," (c113).",{"type":26,"tag":63,"props":691,"children":693},{"id":692},"rules",[694],{"type":35,"value":17},{"type":26,"tag":79,"props":696,"children":697},{},[698,708,718,728,738],{"type":26,"tag":83,"props":699,"children":700},{},[701,706],{"type":26,"tag":31,"props":702,"children":703},{},[704],{"type":35,"value":705},"R1. API first.",{"type":35,"value":707}," The UI reads the API only; no database access from the front end, no Firestore-style live subscriptions unless the API offers a stream.",{"type":26,"tag":83,"props":709,"children":710},{},[711,716],{"type":26,"tag":31,"props":712,"children":713},{},[714],{"type":35,"value":715},"R2. The bible holds.",{"type":35,"value":717}," R13 types, R15 primitives, R23 honesty, R25 staged → served swaps, R31 honest states. The port is a transport swap, not a rewrite.",{"type":26,"tag":83,"props":719,"children":720},{},[721,726],{"type":26,"tag":31,"props":722,"children":723},{},[724],{"type":35,"value":725},"R3. One build, two deployments.",{"type":35,"value":727}," Nothing in the UI knows whether it is SaaS or self-hosted except by configuration.",{"type":26,"tag":83,"props":729,"children":730},{},[731,736],{"type":26,"tag":31,"props":732,"children":733},{},[734],{"type":35,"value":735},"R4. Self-hosted is first-class.",{"type":35,"value":737}," Every flow works with no Qpoint service reachable (updates and telemetry opt-in).",{"type":26,"tag":83,"props":739,"children":740},{},[741,746],{"type":26,"tag":31,"props":742,"children":743},{},[744],{"type":35,"value":745},"R5. Data trust is visible.",{"type":35,"value":747}," The app says where data lives and who runs it (Mark's \"greater data trust\").",{"type":26,"tag":63,"props":749,"children":751},{"id":750},"decisions-defaults",[752],{"type":35,"value":18},{"type":26,"tag":754,"props":755,"children":756},"table",{},[757,781],{"type":26,"tag":758,"props":759,"children":760},"thead",{},[761],{"type":26,"tag":762,"props":763,"children":764},"tr",{},[765,771,776],{"type":26,"tag":766,"props":767,"children":768},"th",{},[769],{"type":35,"value":770},"#",{"type":26,"tag":766,"props":772,"children":773},{},[774],{"type":35,"value":775},"Question",{"type":26,"tag":766,"props":777,"children":778},{},[779],{"type":35,"value":780},"Default",{"type":26,"tag":782,"props":783,"children":784},"tbody",{},[785,823,841,859,885,903,921,939],{"type":26,"tag":762,"props":786,"children":787},{},[788,794,799],{"type":26,"tag":789,"props":790,"children":791},"td",{},[792],{"type":35,"value":793},"D1",{"type":26,"tag":789,"props":795,"children":796},{},[797],{"type":35,"value":798},"Which shell",{"type":26,"tag":789,"props":800,"children":801},{},[802,807,809,814,816,821],{"type":26,"tag":31,"props":803,"children":804},{},[805],{"type":35,"value":806},"qdash's shell",{"type":35,"value":808}," (the global filter, the ",{"type":26,"tag":54,"props":810,"children":812},{"className":811},[],[813],{"type":35,"value":407},{"type":35,"value":815}," pill, the nav ruled through c92–c128) inside ",{"type":26,"tag":31,"props":817,"children":818},{},[819],{"type":35,"value":820},"app.qpoint.io's auth and org frame",{"type":35,"value":822}," (sign-in, org, context, roles).",{"type":26,"tag":762,"props":824,"children":825},{},[826,831,836],{"type":26,"tag":789,"props":827,"children":828},{},[829],{"type":35,"value":830},"D2",{"type":26,"tag":789,"props":832,"children":833},{},[834],{"type":35,"value":835},"Identity",{"type":26,"tag":789,"props":837,"children":838},{},[839],{"type":35,"value":840},"OIDC (works with most IdPs and with self-hosted Keycloak-type servers) plus local accounts for self-hosted; the existing SAML routes kept. No Firebase.",{"type":26,"tag":762,"props":842,"children":843},{},[844,849,854],{"type":26,"tag":789,"props":845,"children":846},{},[847],{"type":35,"value":848},"D3",{"type":26,"tag":789,"props":850,"children":851},{},[852],{"type":35,"value":853},"Multi-tenancy",{"type":26,"tag":789,"props":855,"children":856},{},[857],{"type":35,"value":858},"one org per self-hosted install; SaaS runs one image per customer at first (Tyler: \"we run that on behalf of our customers\"). Multi-org in one instance later.",{"type":26,"tag":762,"props":860,"children":861},{},[862,867,872],{"type":26,"tag":789,"props":863,"children":864},{},[865],{"type":35,"value":866},"D4",{"type":26,"tag":789,"props":868,"children":869},{},[870],{"type":35,"value":871},"Where the code lives",{"type":26,"tag":789,"props":873,"children":874},{},[875,877,883],{"type":35,"value":876},"a new control-plane repo holding the Go app and the UI, the UI moved from ",{"type":26,"tag":54,"props":878,"children":880},{"className":879},[],[881],{"type":35,"value":882},"crates\u002Fqdash\u002Fui",{"type":35,"value":884}," (Tyler rules). app.qpoint.io stays as the reference until the new app replaces it.",{"type":26,"tag":762,"props":886,"children":887},{},[888,893,898],{"type":26,"tag":789,"props":889,"children":890},{},[891],{"type":35,"value":892},"D5",{"type":26,"tag":789,"props":894,"children":895},{},[896],{"type":35,"value":897},"The demo",{"type":26,"tag":789,"props":899,"children":900},{},[901],{"type":35,"value":902},"c113's baked worlds become the SaaS demo and the self-hosted \"try it\" world.",{"type":26,"tag":762,"props":904,"children":905},{},[906,911,916],{"type":26,"tag":789,"props":907,"children":908},{},[909],{"type":35,"value":910},"D6",{"type":26,"tag":789,"props":912,"children":913},{},[914],{"type":35,"value":915},"Scope of design's cycle",{"type":26,"tag":789,"props":917,"children":918},{},[919],{"type":35,"value":920},"the six deliverables above; the port itself is engineering's with design in review, recorded here.",{"type":26,"tag":762,"props":922,"children":923},{},[924,929,934],{"type":26,"tag":789,"props":925,"children":926},{},[927],{"type":35,"value":928},"D7",{"type":26,"tag":789,"props":930,"children":931},{},[932],{"type":35,"value":933},"Alerting and integrations",{"type":26,"tag":789,"props":935,"children":936},{},[937],{"type":35,"value":938},"app.qpoint.io's alerting rules, adapters and integrations are re-homed into qdash's Alerts and Integrations pages, not rebuilt.",{"type":26,"tag":762,"props":940,"children":941},{},[942,947,952],{"type":26,"tag":789,"props":943,"children":944},{},[945],{"type":35,"value":946},"D8",{"type":26,"tag":789,"props":948,"children":949},{},[950],{"type":35,"value":951},"Endpoint connection, grants, messages",{"type":26,"tag":789,"props":953,"children":954},{},[955],{"type":35,"value":956},"the plane exposes a connect endpoint (base URL plus credential exchange), holds mechanic-access grants (c136 D1) and the operator's message inbox (c137 D1). All three are in the API contract (Phase 2).",{"type":26,"tag":63,"props":958,"children":960},{"id":959},"phases",[961],{"type":35,"value":19},{"type":26,"tag":963,"props":964,"children":966},"h3",{"id":965},"_0-inventory-can-run-now",[967],{"type":35,"value":968},"0. Inventory (can run now)",{"type":26,"tag":27,"props":970,"children":971},{},[972,977],{"type":26,"tag":54,"props":973,"children":975},{"className":974},[],[976],{"type":35,"value":594},{"type":35,"value":978},": both apps mapped, the API gap list, the backend asks consolidated.",{"type":26,"tag":963,"props":980,"children":982},{"id":981},"_1-the-shell",[983],{"type":35,"value":984},"1. The shell",{"type":26,"tag":27,"props":986,"children":987},{},[988,990,996],{"type":35,"value":989},"One lab mock (",{"type":26,"tag":54,"props":991,"children":993},{"className":992},[],[994],{"type":35,"value":995},"\u002Fpages\u002Fphase4\u002Fmock\u002F...",{"type":35,"value":997}," or a new lab section): sign-in → org → the qdash home with the global filter. Review stop.",{"type":26,"tag":963,"props":999,"children":1001},{"id":1000},"_2-the-contract",[1002],{"type":35,"value":1003},"2. The contract",{"type":26,"tag":27,"props":1005,"children":1006},{},[1007,1009,1014],{"type":35,"value":1008},"The API document from ",{"type":26,"tag":54,"props":1010,"children":1012},{"className":1011},[],[1013],{"type":35,"value":436},{"type":35,"value":1015},", the routes, the asks, auth and scoping, the window. Reviewed with Tyler and Kamal. This is the hand-off that lets Kamal start.",{"type":26,"tag":27,"props":1017,"children":1018},{},[1019],{"type":35,"value":1020},"The six endpoint-only shapes the native app needs (sensor state, resources, effective config, log tail, config file, plane connection) are the document's first new entries: ruled into qdash routes typed in TypeScript and generated into Rust like the other 38 (c131 D17, 2026-11-11), so there is one mechanism for all 44 shapes.",{"type":26,"tag":963,"props":1022,"children":1024},{"id":1023},"_3-identity-and-install",[1025],{"type":35,"value":1026},"3. Identity and install",{"type":26,"tag":27,"props":1028,"children":1029},{},[1030],{"type":35,"value":1031},"Sign-in, invite, SSO, local accounts; first run and connect-the-first-endpoint, on the wizard kit. Mocked in the lab, reviewed one at a time. Install docs with Mark Peterson.",{"type":26,"tag":963,"props":1033,"children":1035},{"id":1034},"_4-the-port-plan-g4",[1036],{"type":35,"value":1037},"4. The port plan (G4)",{"type":26,"tag":27,"props":1039,"children":1040},{},[1041],{"type":35,"value":1042},"Repo, build, Docker, CI, cut-over, demo. Accepted by Tyler and Kamal.",{"type":26,"tag":963,"props":1044,"children":1046},{"id":1045},"_5-the-port-with-engineering",[1047],{"type":35,"value":1048},"5. The port, with engineering",{"type":26,"tag":27,"props":1050,"children":1051},{},[1052],{"type":35,"value":1053},"The UI moves; transports swap to the new API; design reviews each page against the lab; the record here.",{"type":26,"tag":63,"props":1055,"children":1057},{"id":1056},"clarifying-questions",[1058],{"type":35,"value":20},{"type":26,"tag":580,"props":1060,"children":1061},{},[1062,1067,1072,1077,1082,1087],{"type":26,"tag":83,"props":1063,"children":1064},{},[1065],{"type":35,"value":1066},"Where does the code live (D4), and when is Kamal free?",{"type":26,"tag":83,"props":1068,"children":1069},{},[1070],{"type":35,"value":1071},"Which of Pulse's patterns survive (the filter, the views)? Is Pulse's source available to read?",{"type":26,"tag":83,"props":1073,"children":1074},{},[1075],{"type":35,"value":1076},"Identity: OIDC + local accounts acceptable for self-hosted, or does Tyler have a plan?",{"type":26,"tag":83,"props":1078,"children":1079},{},[1080],{"type":35,"value":1081},"One org per install (D3), or multi-org from the start?",{"type":26,"tag":83,"props":1083,"children":1084},{},[1085],{"type":35,"value":1086},"Does app.qpoint.io's alerting move (D7), or does qdash's Alerts page replace it?",{"type":26,"tag":83,"props":1088,"children":1089},{},[1090,1092,1097],{"type":35,"value":1091},"What happens to ",{"type":26,"tag":54,"props":1093,"children":1095},{"className":1094},[],[1096],{"type":35,"value":687},{"type":35,"value":1098}," and the baked worlds (D5)?",{"type":26,"tag":63,"props":1100,"children":1102},{"id":1101},"patterns-to-consider",[1103],{"type":35,"value":21},{"type":26,"tag":79,"props":1105,"children":1106},{},[1107,1117,1127,1137,1147,1157],{"type":26,"tag":83,"props":1108,"children":1109},{},[1110,1115],{"type":26,"tag":31,"props":1111,"children":1112},{},[1113],{"type":35,"value":1114},"Generate the API types from the Go handlers",{"type":35,"value":1116}," (or from a shared schema), and generate the TypeScript and Rust clients from them, so the native app and the web app share one contract (c131 R3).",{"type":26,"tag":83,"props":1118,"children":1119},{},[1120,1125],{"type":26,"tag":31,"props":1121,"children":1122},{},[1123],{"type":35,"value":1124},"The Connect flow as the join",{"type":35,"value":1126},": the native app signs an endpoint in; the web app shows it arrive. Design both halves as one story.",{"type":26,"tag":83,"props":1128,"children":1129},{},[1130,1135],{"type":26,"tag":31,"props":1131,"children":1132},{},[1133],{"type":35,"value":1134},"The install as a tour",{"type":35,"value":1136}," (c125): the first-run wizard and the operations journey are the same path.",{"type":26,"tag":83,"props":1138,"children":1139},{},[1140,1145],{"type":26,"tag":31,"props":1141,"children":1142},{},[1143],{"type":35,"value":1144},"A self-hosted \"health\" page",{"type":35,"value":1146},": the two databases, the version, the last update check; honest states for an operator who runs it themselves.",{"type":26,"tag":83,"props":1148,"children":1149},{},[1150,1155],{"type":26,"tag":31,"props":1151,"children":1152},{},[1153],{"type":35,"value":1154},"Messages are a resource",{"type":35,"value":1156}," (c137): the operator's inbox and send are plane objects in the same contract, not a side channel.",{"type":26,"tag":83,"props":1158,"children":1159},{},[1160,1165,1166,1172],{"type":26,"tag":31,"props":1161,"children":1162},{},[1163],{"type":35,"value":1164},"The pipeline map",{"type":35,"value":92},{"type":26,"tag":54,"props":1167,"children":1169},{"className":1168},[],[1170],{"type":35,"value":1171},"brand\u002Fpipeline.md",{"type":35,"value":1173},") gets its stage 7 rewritten: production install = the control plane image and the native app.",1794599399743]