| // --------------------------------------------------------------------------- | |
| // shell/dbFrame.tsx β THE DATABASE FRAME, extracted so a SECOND surface can wear it. | |
| // | |
| // β WHY THIS FILE EXISTS. The owner's instruction for the Query module is one | |
| // sentence: *"the query module should exactly BE looking like the database module, | |
| // COMPLETELY, with all the views etc. The only difference is that user isn't the one | |
| // creating the view, it's the AI that they prompt in the AI assistant module."* | |
| // | |
| // "Exactly" and "completely" are only satisfiable by mounting THE SAME frame and THE | |
| // SAME grid β `shell-db-frame` + `DbHead` + `OverlayProvider` + `CustomerGrid`. A | |
| // hand-rolled view list beside it would be a second, thinner answer to a question this | |
| // product already answers. So the header and the topicβscope map moved OUT of | |
| // `Shell.tsx` (where they were private) and into here, and both call sites import them. | |
| // | |
| // β `gridScopeFor` IS THE WHOLE REASON THIS IS NOT JUST A COMPONENT MOVE. A registry | |
| // key and a grid scope are DIFFERENT SPELLINGS of the same database β `product_data` is | |
| // the key, `product` is the scope; `customer_data` is the key, `customer` is the scope; | |
| // a `ut_` key is its own scope. That map lived inline in one branch of `Shell.tsx`, and | |
| // the Query module's saved rows carry the KEY (`routes_query.BUILTIN_SCOPES = | |
| // ('customer_data', 'product_data')`). So a second inline copy β or worse, passing the | |
| // key straight through β is not a tidiness question: | |
| // Β· `CustomerGrid`'s VIEW_OPEN listener guards on `detail.topic !== scope`, i.e. the | |
| // SCOPE spelling, and drops anything else SILENTLY (CustomerGrid.tsx:3151); | |
| // Β· so an emit carrying `customer_data` at a grid mounted as `customer` never selects | |
| // the view, and the click "worked". | |
| // One question, one normaliser ([[one-question-two-normalizers]]). | |
| // --------------------------------------------------------------------------- | |
| import type { ReactNode } from "react"; | |
| import { FolderMark } from "../customer-grid/icons"; | |
| import type { SurfaceScope } from "../customer-grid/apiBridge"; | |
| import type { FolderIcon } from "../customer-grid/types"; | |
| import { dbChipClass } from "./nav"; | |
| /** The cylinder every database wears when it has never been given a mark. */ | |
| export function DbIcon() { | |
| return ( | |
| <svg className="shell-nav-icon" viewBox="0 0 16 16" aria-hidden="true"> | |
| <ellipse cx="8" cy="3.6" rx="5.3" ry="2.1" /> | |
| <path d="M2.7 3.6v8.8c0 1.16 2.37 2.1 5.3 2.1s5.3-.94 5.3-2.1V3.6" /> | |
| <path d="M2.7 8c0 1.16 2.37 2.1 5.3 2.1S13.3 9.16 13.3 8" /> | |
| </svg> | |
| ); | |
| } | |
| /** | |
| * A nav/registry KEY β the scope `CustomerGrid` is mounted with. | |
| * | |
| * β THE ONE PLACE THIS MAP IS WRITTEN. See the header: the two spellings are silently | |
| * interchangeable-looking and are not interchangeable, and the failure they produce is a | |
| * dropped event rather than an error. | |
| * | |
| * β `cohort` is a legal `SurfaceScope` and is deliberately NOT a case here: the `#/cohort` | |
| * route left with its registry row in wave 16 (cohorts project as LOCKED VIEWS in the | |
| * Customer rail), so no nav key resolves to it. The scope stays reachable server-side; it | |
| * simply is not something a nav row names any more. | |
| */ | |
| export function gridScopeFor(key: string): SurfaceScope { | |
| if (key === "product_data") return "product"; | |
| if (key.startsWith("ut_")) return key as `ut_${string}`; | |
| return "customer"; | |
| } | |
| /** | |
| * WAVE 20 item 4 (R8 / C-ADDROW) β THE UNIVERSAL DATABASE HEADER. | |
| * | |
| * One header, identical on every database (`reference/Airtable 6.png`): a chip in | |
| * a bold colour carrying the database's mark, then its name. It replaces | |
| * `shell-ut-bar`, which existed on USER TABLES only β so the two built-in | |
| * databases had no header at all, and the one place the product named the thing | |
| * you were looking at appeared or vanished depending on where the table came | |
| * from. R8 makes it universal. | |
| * | |
| * The "Add record" button the old bar carried is GONE with it, deliberately: R8 | |
| * replaces it with the grid's own trailing "+" row (S3's half of C-ADDROW), on | |
| * user databases only β a connector-backed table's rows are read-synced, and a | |
| * "+" that must refuse is a fake affordance. | |
| * | |
| * The chip is decorative and says so: the name beside it is real text, so a | |
| * second announcement of the same fact is noise to a screen reader. | |
| * | |
| * β `aside` (added with this file's extraction) is the ONE addition, and it changes | |
| * nothing for the existing caller, which passes none. The Query module needs a control in | |
| * this row β you cannot look at a database without saying WHICH β and putting it here is | |
| * what keeps that surface the same frame rather than a frame with a strip bolted above it. | |
| */ | |
| export function DbHead({ label, icon, glyph, aside }: { | |
| label: string; | |
| icon?: FolderIcon; | |
| glyph?: ReactNode; | |
| aside?: ReactNode; | |
| }) { | |
| return ( | |
| <div className="shell-db-head"> | |
| <span className={dbChipClass(icon)} aria-hidden="true"> | |
| {/* The database's own mark when it has one, the cylinder when it does not β | |
| the same pair the rail row draws, so the header and the nav agree. Both | |
| paint in the chip's ink (the stylesheet's two overrides): `FolderMark` | |
| would otherwise stroke its pastel `-deep`, which is measured against the | |
| WHITE rail and disappears on its own tone. | |
| WAVE 23 C13 β `glyph` overrides only the FALLBACK, never a chosen mark, and | |
| that ordering is the contract. Automation is not a database and must not wear | |
| the cylinder; but it CAN wear a `nav_meta` icon (the rail already draws one, | |
| Shell:1026), and a header that ignored it would be the one surface where the | |
| rail and the frame disagreed about the same row. So: chosen icon > caller's | |
| glyph > the cylinder. */} | |
| {icon ? <FolderMark icon={icon} size={16} /> : (glyph ?? <DbIcon />)} | |
| </span> | |
| {/* An `h1`, not a styled span: this is the first time the work surface has NAMED itself, | |
| and the name of the thing you are looking at is what a heading is for. Every other | |
| full-pane surface in this shell (`shell-placeholder`) already uses one, and they never | |
| render together β so the page gains a heading rather than a second one. */} | |
| <h1 className="shell-db-name">{label}</h1> | |
| {aside ?? null} | |
| </div> | |
| ); | |
| } | |