// --------------------------------------------------------------------------- // settings / ModulePermsList.tsx — THE per-database permission list. (wave 33, // owner items 10 + 11 · W33-T34 / W33-T38.) // // ⭐ WHY THIS FILE EXISTS, IN THE OWNER'S OWN WORDS: *"under manage user (and // manage agent too since its the same exact layout)"*. "The same exact layout" // has exactly one honest implementation, and it is not two components kept in // step by intention. `PermsEditor` and `ManageAgentPane` both render THIS, so a // change to the disclosure or the summary line reaches both or neither — and // wave 36 is the proof: deleting the "Not set here" card (W36-T22 / C2, owner // item 11) took it out of the agent pane too, in the same edit, with nothing to // remember. // // ⛔ THE ALTERNATIVE, NAMED SO NOBODY RE-DERIVES IT. Copying the JSX into the // agent pane would have been faster today and is the exact shape of every defect // this repo keeps re-finding: `may_open` vs the automation table picker (wave // 20), `clean_fields` vs `_clean_field` (wave 28), `FILTER_OPS`' three mirrors. // Two renderings of one rule disagree in front of a user, and each looks correct // read on its own. // // PURELY PRESENTATIONAL. It owns exactly one piece of state — WHICH row is // expanded — because that is a property of this list and of nothing else. Every // value it shows and every write it makes belongs to the caller: the account // editor PUTs to `/admin/users/{u}/perms`, the agent editor to its own door, and // this component knows about neither. // --------------------------------------------------------------------------- import { useState } from "react"; import type { ReactNode } from "react"; import { FilterBuilderPanel, FieldsHidePanel } from "../filter-kit"; import type { FilterTree } from "../customer-grid/types"; import type { PermsModule, PermsRecord } from "./permsModel"; import { filterOf, hiddenSet, hideableKeys, identityKey, moduleSummary } from "./permsModel"; import "./perms.css"; export interface ModulePermsListProps { /** In server order — the order the sections render in. */ modules: PermsModule[]; /** The DRAFT being edited, keyed by module. */ entries: PermsRecord; onAccess: (key: string, on: boolean) => void; onFilter: (key: string, next: FilterTree | null) => void; onToggleHidden: (key: string, fieldKey: string) => void; onSetHidden: (key: string, keys: string[]) => void; /** People this tenant can name in a `user` condition. Absent ⇒ the panel says so. */ userOptions?: string[]; /** Anything the CALLER wants in a row's head — the account editor's "Copy to…" * door. Returning null is the normal case and costs the row nothing. */ headExtra?: (m: PermsModule) => ReactNode; /** Shown when the tenant governs nothing at all. */ emptyNote?: string; } export function ModulePermsList({ modules, entries, onAccess, onFilter, onToggleHidden, onSetHidden, userOptions, headExtra, emptyNote, }: ModulePermsListProps) { // ⭐ Owner item 11: *"the database should not show all immediately the detail // (for filter or hide fields)"*. Single-valued rather than a Set, deliberately // — the complaint was a wall of panels, and an accordion cannot become one by // accumulation. const [openDetail, setOpenDetail] = useState(null); if (modules.length === 0) { return (

{emptyNote ?? "This workspace has no databases whose access can be restricted."}

); } return ( <> {modules.map((m) => { const entry = entries[m.key]; const on = entry?.access === true; const schemaless = m.fields.length === 0; const shown = openDetail === m.key; // ⭐⭐ WAVE 36 (W36-T22 / CONTRACT C2 / OWNER RULING R6) — THE `enforced` // BRANCH AND ITS "Not set here" CARD ARE DELETED. Owner item 11, verbatim: // *"how come the database is only toggleable for Odoo customers and Odoo // products. EVERY database should be able to be toggleable by admin. I'm // only seeing 'Not set here'."* // // That card was HONEST when it shipped — `perm_scope` was never consulted // on a `ut_*` read, so an access toggle there would have been a control // that lies. W36-T21 armed the wall over every database, so the branch's // premise is now false and rendering it would be the lie it was written to // prevent, facing the other way. Every row below is a row whose rule the // table routes apply. const canDetail = on && !schemaless; return (
{/* ⚠ Wrapped rather than left as siblings of the toggle: `.set-perm-modhead` is `space-between` and lives in `index.css`, which is lane B's under contract C4. Grouping the trailing controls keeps every new one off that rule — one flex child in, one flex child out. */} {moduleSummary(entry, schemaless)} {canDetail ? ( ) : null} {headExtra?.(m) ?? null}
{/* R9's fail-closed rendering: no readable schema means the access toggle and nothing else. The record it saves says the same thing — no filter, no hidden fields — so the editor and the payload cannot disagree (`permsModel.toPutBody`). */} {on && schemaless ? (

No field list is available for this database, so access is all this editor can set for it. Conditions and hidden fields need a schema.

) : null} {canDetail && shown ? (
onFilter(m.key, next)} userOptions={userOptions} />
) : null}
); })} ); }