# GB5 BI Platform Catalog

One entry per capability. Each entry has four sections — read the one for your role, skip the
rest:

- **For End Users** — plain-language: what you see and when you'd use it.
- **For Client Admin / ImpTeam** — what's configurable today, what isn't yet.
- **For Developers** — routes, files, technical notes and known gotchas.
- **Status** — what's actually been verified, and how (not just "done").

---

## 1. BI Catalog Registry & Query Engine

**For End Users**: Not something you interact with directly — this is the machinery that makes
every widget below possible, no matter which of the three source kinds (below) the widget's data
actually comes from.

**For Client Admin / ImpTeam**: A "catalog" is a registered, named thing a BI View can point at
(e.g. "Sales (Test)," or an existing report someone turned into a catalog). Catalogs come in three
kinds — see the Overview's "Where data can come from" for the full explanation:
- **Warehouse** — a governed fact table. Two test catalogs exist on GB5DEMO today: a Sales fact (15
  measures) and a Finance fact (18 measures). Registering a brand-new Warehouse catalog is still a
  developer/migration task.
- **ApiService** — any existing GB5 report, turned into a catalog via `CreateBICatalogFromReportView`
  (backend live-verified; no config-screen button for this yet — see entry 7).
- **AnalysisQuery** — a bridge into the separate ad-hoc query designer's own field/aggregation setup.

**For Developers**:
- Table: `MBICATALOG` (renamed from `MANALYSISCATALOG`) — one row per catalog, `DATASETKIND`
  distinguishes the three kinds: `0 = Warehouse`, `1 = AnalysisQuery`, `2 = ApiService`.
- Resolution: `IDatasetResolver.ResolveAsync(biCatalogId, login, ct)` is the one place that knows
  how to resolve any `DatasetKind` — always call this, never re-derive catalog→source resolution
  logic elsewhere.
- Measures (Warehouse-kind): `MWAREHOUSEMEASURE` — one row per queryable numeric column, with
  `AdditivityType` (0=Additive, 1=SemiAdditive, 2=NonAdditive), `DefaultAggregation`, `IsVisible`.
- Measures/Dimensions (ApiService-kind): `MBIFIELDMAPPING` (renamed from
  `MANALYSISFIELDMAPPING`) — the field-mapping registry, see entry 7.
- Dimensions/Measures (AnalysisQuery-kind): read live from `MANALYSISQUERYFIELDS` for the
  resolved query — `SUMMARY`-zone fields (with their `AGGREGATIONTYPE`) map to Measures,
  `HORIZONTAL`/`VERTICAL`/`INFO` zones map to Dimensions; `FILTER`/`COMPARISON` zones are excluded
  from the picker. Raw-SQL (`QUERYTYPE=DIRECT`) ad-hoc queries are excluded entirely (no stable
  field list can be trusted from raw SQL) — this exclusion happens in the existing
  `DatasetResolver.ResolveAnalysisQueryAsync`, not newly added here.
- Dimensions (Warehouse-kind): **not** a DB-backed registry (a deliberate v1 simplification) —
  `WarehouseDimensionCatalog` (a static C# class) hardcodes the allowed dimension fields.
- Query execution: `POST /BI/RunBIQuery` (renamed from `/Analysis/RunAnalysisQuery`) — takes an
  `AnalysisQueryDefinition` (DatasetId, Dimensions, Measures, Filters, Rows, Columns, DrillPath,
  Sort, TopN), returns an `AnalysisResult`. `Filters`/`Sort` remain unconsumed server-side;
  `Rows`/`Columns` are now consumed, but only client-side by the pivot-table adapter (see entry 5)
  — the query engine itself still returns flat rows regardless of widget type.
- **Separate, pre-existing system, still not to be confused with this one**: the ad-hoc
  `MANALYSIS`/`MANALYSISQUERY` drag-and-drop designer (its own FE at
  `projects/analytics/master/analysisquerydesigner.component.ts`) remains untouched — the
  AnalysisQuery-kind catalog above *reads from* it, it doesn't replace or modify it.

**Status**: Backend engine (resolver, registry, `RunBIQuery`) live-verified against GB5DEMO for
Warehouse-kind (both test catalogs) and ApiService-kind (pilot catalog and a freshly-created
report-backed catalog) with real HTTP calls. AnalysisQuery-kind's picker branch is code-complete
but has **not** been live-tested against a real ad-hoc query — no test fixture attempted yet.
Config-UI-facing picker endpoints (`GetSelectListBICatalog`, `GetDimensionsForDataset`,
`GetMeasuresForDataset`) live-verified via authenticated HTTP calls. Two real bugs were caught and
fixed during this and an earlier verification pass:
1. The Dimensions and Measures endpoints once generated an identical cache key for the same
   catalog id, so whichever was called first silently served its result to the other (fixed with a
   distinguishing suffix).
2. A rename of this engine's route prefix (`/Analysis/*` → `/BI/*`) orphaned a catalog's own
   stored callback path, silently 404ing every query against it until caught live and fixed.

---

## 2. KPI Card Widget

**For End Users**: A single number in a card — e.g. "Total Sales Value: 164,516,887.68" — for when
a widget has no dimension to break the measure down by, or when a saved BI View deliberately
specifies just the headline figure.

**For Client Admin / ImpTeam**: Reachable two ways — automatically, when a widget's query has zero
dimensions selected, or explicitly, by authoring a BI View with Widget Type "KPI Card."

**For Developers**:
- `features/gb5widget/adapters/kpi-card-adapter/kpi-card-adapter.component.ts`.
- Deliberately never aggregates client-side, even on unexpected multi-row data — a measure's
  `Additivity` (especially semi-additive point-in-time balances) makes summing leftover rows
  actively wrong, not just imprecise. On unexpected multi-row data it shows row 0 plus a visible
  truncation caption instead.
- Dispatched automatically by `Gb5WidgetComponent.resolvedType()` when `DimensionsUsed.length === 0`
  and no explicit `WidgetType` override is present.

**Status**: Code-complete, unit-tested (`.spec.ts`), live-verified rendering against real
`RunBIQuery` data as part of this engine's original development.

---

## 3. Chart Widget (ApexCharts)

**For End Users**: A bar, donut, line, pie, or area chart — one dimension on the axis/categories,
one or more measures as the series. Which chart sub-type you get is picked automatically from the
shape of the data (a date dimension → line; ≤7 categories → donut; more → bar), not chosen by you.

**For Client Admin / ImpTeam**: Reachable automatically when a widget has exactly one dimension
selected, or explicitly via a BI View authored with Widget Type "Chart."

**For Developers**:
- `features/gb5widget/adapters/apex-charts-adapter/apex-charts-adapter.component.ts`.
- Deliberately simple relative to the legacy `GBDynamicChartComponent`: no multi-dimension
  grouped/stacked series support — it only ever reads `Meta.DimensionsUsed[0]`. This is exactly why
  a 2+-dimension result needs its own adapter (entry 4) rather than being force-fit into this one.
- Sub-type recommendation: `recommendChartSubType()` in `gb5-chart-recommender.util.ts`.
- Dispatched by `resolvedType()` when exactly 1 dimension is present and no `WidgetType` override
  forces something else.

**Status**: Code-complete, unit-tested. The data pipeline feeding it has been independently
re-confirmed multiple times with real data. Pixel-level chart rendering has not been re-confirmed
in a browser this session — see entry 9's Status for the current dev-server blocker.

---

## 4. Table Widget (ag-Grid)

**For End Users**: A flat data grid — one column per dimension and measure, one row per result
row. This is what you get for any query with 2 or more dimensions, where a single chart axis
doesn't make sense, or a BI View authored with Widget Type "Table."

**For Client Admin / ImpTeam**: Reachable automatically for 2+-dimension queries, or explicitly
via Widget Type "Table" in the BI View author flow.

**For Developers**:
- `features/gb5widget/adapters/table-adapter/table-adapter.component.ts` — a minimal `ag-Grid`
  wrapper (`ag-grid-community`, already a repo dependency), deliberately **not** a reuse of the
  existing `features/gbgrid/GbGridComponent` (coupled to dashboard/pubsub concerns `gb5-widget`'s
  adapters must never import).
- **Not a pivot/crosstab.** One column per `Meta.DimensionsUsed`/`MeasuresUsed` entry, one row per
  `AnalysisResult.Data` entry — for an actual Excel-style crosstab, see entry 5.
- Dispatched by `resolvedType()` when 2 or more dimensions are present and no override forces
  something else.

**Status**: Code-complete, `tsc --noEmit` clean, unit-tested. No dedicated browser click-through
attempted specifically for this adapter; treat as "should work, not pixel-verified."

---

## 5. Pivot Table Widget (WebDataRocks)

**For End Users**: A real, Excel-style pivot table — drag a field between the row axis and the
column axis, see subtotals recompute live, expand/collapse groups, export to Excel — the same kind
of interaction as a spreadsheet pivot, not a flat grid. What you can do with the toolbar depends on
whether you're authoring the widget (more controls) or just viewing it on a dashboard (fewer, see
below).

**For Client Admin / ImpTeam**: Reachable by authoring a BI View with Widget Type "Pivot Table," or
by picking an existing pivot-type BI View. See the Admin Config Guide's "Working with a pivot
table" section for the full authoring walkthrough, including how to save a rearranged layout.

**For Developers**:
- `features/gb5widget/adapters/pivot-table-adapter/pivot-table-adapter.component.ts`, wrapping
  `@webdatarocks/webdatarocks` (core + toolbar), loaded as classic global `<script>` tags via
  `angular.json`'s `scripts` array — **not** an ES import (the toolbar module references its
  sibling script's globals directly; an ES import risks the bundler double-loading the runtime in
  a module-scoped copy the toolbar's bare-global reference can't see into). Reachable only via an
  explicit `widgetType` override — `gb5-widget`'s dimension-count auto-routing has no slot for it.
- **This is a client-side pivot, not a backend crosstab.** WebDataRocks does its own row/column
  axis-splitting and re-aggregation entirely in the browser, from the exact same flat
  `AnalysisResult.Data` every other adapter renders directly — `RunBIQuery` and
  `AnalysisAggregationService` are completely unaware a pivot is even happening. This is a
  materially simpler approach than an earlier draft of the Developer Playbook once proposed (a new
  backend aggregation path + a new 2D response shape) — see that document's own correction.
- Each measure's server-computed `Aggregation` code is mapped to the WebDataRocks re-aggregation
  operator that stays mathematically correct given already-aggregated data: SUM/COUNT→`sum`,
  MAX→`max`, MIN→`min`; AVG/None, and any measure flagged `NonAdditive` regardless of its
  aggregation code, are rendered as the already-correct per-row value with re-aggregation disabled
  (`individual: true`) rather than letting WebDataRocks silently re-sum a value that isn't safe to
  sum (see the Overview's "governed" framing — the same additivity discipline, enforced here too).
- **Toolbar modes** (`mode: 'design' | 'run'` input, default `'run'`): a `beforetoolbarcreated`
  callback filters WebDataRocks' own tab list. Both modes always hide `wdr-tab-connect`/`open`/
  `save` (local-file operations that never fit a server-driven system). `run` mode additionally
  hides `wdr-tab-format`/`wdr-tab-options`, leaving fields/export/fullscreen.
- **"Save current layout"** — a `saveLayout` output (design mode only) reads the pivot's current
  Rows/Columns via `getReport()` and emits them; the host component persists them into the owning
  `MBIVIEW.AdapterConfigJson.pivotSlice`, which then wins over the view's own default
  Rows/Columns on every subsequent render (both in the config screen's own preview and on a real
  dashboard) — this is what makes a manual rearrangement survive a reload.

**Status**: Code-complete, `dotnet build`/`tsc --noEmit` clean throughout the backend
(`MBIVIEW.AdapterConfigJson` round-trip) and frontend. Toolbar-mode filtering and the save-layout
emission logic are covered by targeted unit tests asserting exactly which WebDataRocks tab ids
survive each mode's filter, and that `onSaveLayoutClick()` emits the correct Rows/Columns read
from a mocked `getReport()`. **No live browser click-through** — three attempts to run a local dev
server against GB5DEMO hit this environment's known, pre-existing memory-ceiling failure (an
`assert(compilation)` esbuild/Angular-compiler crash, root-caused in an earlier session to this
machine's 8GB RAM limit, not this feature's code); ruled out a stale-process theory specifically
during this attempt. Treat the actual pixel-level rendering, drag-and-drop rearrangement, and
save-layout round-trip as "should work per the code and its tests, not yet visually proven."

---

## 6. BI Views (`MBIVIEW`) — saved, reusable definitions

**For End Users**: Not something you configure directly — this is what makes the widget you see on
a dashboard reusable and consistent, and what remembers a pivot table's arrangement between visits.

**For Client Admin / ImpTeam**: A BI View is a saved Dimension/Measure/Widget-Type definition over
one catalog, independent of any one dashboard placement — author it once, point any number of
Portlets at it. See the Admin Config Guide for the authoring walkthrough.

**For Developers**:
- Table: `MBIVIEW` — new, independent table (not a child of `MPORTLET`), mirroring how
  `MREPORT`↔`MREPORTVIEW` separates a report from its views. One `MBICATALOG` can back many
  `MBIVIEW` rows. `WIDGETTYPE` is stored as the literal `GB5WidgetType` wire string
  (`'kpi-card'`/`'apex-chart'`/`'table'`/`'pivot-table'`), not a numeric enum — there's no existing
  numeric convention worth mirroring for a value the FE consumes directly in a `@switch`.
  `BIDEFINITIONJSON` holds the serialized `AnalysisQueryDefinition`; `ADAPTERCONFIGJSON` holds the
  serialized `GB5WidgetAdapterConfig` including a saved pivot layout.
- `MPORTLET.BIVIEWID` — a lightweight, nullable FK pointer, the same idiom `MPORTLET` already uses
  for `DefaultCriteriaConfigId`/`WebServiceSettingId`. `NULL` means "legacy portlet, pre-dating BI
  Views, still using the flat `PortletChartXAxis`/`PortletChartYAxis` pair" —
  `Gb5WidgetPortletComponent`'s fallback path, never backfilled automatically.
- BLL: `BIViewBLL.Save`/`GetBIView`/`GetSelectListByCatalogId`/`Delete`
  (`AnalyticsBLL/BIView/BIViewBLL.cs`). `Save` re-runs the same server-side Additivity/Aggregation
  trust check `RunBIQuery` applies at query time (`IAnalysisAggregationService.ValidateDefinitionAsync`)
  — a bad definition is rejected at save time, not left dormant until a dashboard hits it live.
  Widget-type-vs-PortletType governance (entry 8) is deliberately **not** checked here — a BIView
  has no PortletTypeId of its own; that check belongs where a Portlet actually adopts a BIViewId.
- Endpoints: `POST /BI/SaveBIView`, `GET /BI/GetBIView?BIViewId=`,
  `GET /BI/GetSelectListBIView?BICatalogId=`, `DELETE /BI/DeleteBIView?BIViewId=`.

**Status**: Live-verified end-to-end on GB5DEMO with real HTTP calls — save (first-ever real
`AutoNumber`-driven insert for this entity), fetch, list, and delete all confirmed correct,
including a real bug found and fixed live: `BIViewBLL.Save`/`Delete` initially never invalidated
the cache behind `GetSelectListBIView`/`GetBIView`, so a freshly-saved view kept being invisible to
the list picker until the cache separately expired. Fixed and re-verified (save → immediately
visible; delete → immediately absent).

---

## 7. Generalized Field-Mapping — turning an existing report into a catalog

**For End Users**: Not something you interact with — this is what lets an admin turn a report you
already use into something you can also see as a chart or pivot, without a developer rebuilding
it.

**For Client Admin / ImpTeam**: Two related pieces:
1. **Turning a report into a catalog** (`CreateBICatalogFromReportView`) — point at an existing,
   non-pivoted report, give the new catalog a code and name, and it's registered, pointing at that
   report's own callable address. A report whose columns are computed at run time (a data-pivot
   report) is rejected immediately with a clear reason — see the Overview's "structural exclusion."
   **Not yet exposed as a config-screen button** — today this is an API call an implementation team
   member makes on a client's behalf, not a self-service screen action.
2. **Field-mapping review** (`SuggestBIFieldMappings`/`SaveBIFieldMapping`) — once a report-backed
   catalog exists, an admin samples it live and reviews/edits the suggested Dimension/Measure
   classification before the catalog's picker has anything to offer. See the Admin Config Guide's
   step 7 for the click-through.

**For Developers**:
- `BICatalogBLL.CreateFromReportView` reads `MREPORTVIEW`/`MREPORT` via
  `GET_REPORTVIEW_FOR_CATALOG_CREATION`, rejects `ISDATAPIVOT=1` views, and resolves the new
  catalog's callable address with a two-step fallback:
  1. `MREPORT.REPORTURI`, if it's a real value (not empty, not the literal sentinel `'NONE'` —
     confirmed live that ~99.7% of real GB5DEMO reports leave this as `'NONE'`).
  2. Otherwise, `MREPORT.WEBSERVICEID` → `MWEBSERVICE.URITEMPLATE` (confirmed live: the real
     majority of reports — 1055/1085 on GB5DEMO — resolve their actual backing address this way
     instead). This fallback is only accepted if it's a flat, parameter-free address — it's
     **rejected with a clear, named error** (naming the web service and the unusable URI) if it
     contains an unresolved `{placeholder}`, a `.svc` segment, or a `/gb4/` path segment — the
     signature of a legacy GB4 WCF endpoint, which needs base-URI and path-parameter resolution
     this engine's HTTP calling code (`IExcelExport.ReportURICalling`, an unconditional POST with
     zero placeholder substitution) does not do. On GB5DEMO today, essentially every real
     `MWEBSERVICE`-backed report falls into this rejected category — the rejection message is what
     most real report-to-catalog attempts will actually see until a future pass adds real
     legacy-endpoint calling support, if that's ever prioritized.
  A new catalog's data source is always the shared `GB5_SELF_HOST` `MDATASOURCE` row (resolved by
  code, not a hardcoded id) — one row serves every report-backed catalog on a given host.
- `BIFieldMappingBLL.SuggestMappings` reuses the exact same `IApiDatasetDAL.FetchRowsAsync` sample
  call `RunBIQuery` already uses for ApiService-kind catalogs, infers a shape from the first
  returned row, and always guesses conservatively for measures (SUM/Additive — never AVG/NonAdditive)
  since a wrongly-guessed "safe to sum" is far less dangerous than a wrongly-guessed "safe to
  average." The guess is never auto-saved; `SaveMapping` is a separate, explicit admin action.
- Endpoints: `POST /BI/CreateBICatalogFromReportView`, `GET /BI/SuggestBIFieldMappings?BICatalogId=`,
  `POST /BI/SaveBIFieldMapping`.

**Status**: Live-verified end-to-end on GB5DEMO, both branches of the fallback:
1. A clean test fixture (a minimal `MREPORT`/`MREPORTVIEW` pair pointing at this engine's own
   ApiService pilot endpoint) confirmed the direct-`ReportUri` path, catalog creation, and the full
   suggest→save field-mapping flow, including its own cache-invalidation fix (same class of bug as
   entry 6 — `SaveMapping` didn't invalidate the dimension/measure picker cache either; fixed and
   re-verified the same way).
2. A real, existing GB5DEMO report ("Account Group," backed by a real legacy `MWEBSERVICE` row)
   confirmed the rejection path produces the intended clear, named error instead of a confusing
   HTTP-layer failure or a generic message.

---

## 8. Widget Type Governance (`SupportedWidgetTypes`)

**For End Users**: Not visible to you directly — this is why some Portlet Types only ever offer
certain widget shapes.

**For Client Admin / ImpTeam**: See the Admin Config Guide's "Widget Type restrictions" — this is
configured once per Portlet Type, not per widget instance.

**For Developers**:
- `MPORTLETTYPE.SUPPORTEDWIDGETTYPES` — a comma-separated `GB5WidgetType` allow-list, materialized
  from `PortletTypeVersion`'s existing `Capabilities` activation flow (the same mechanism that
  already flattens `IsMaximize`/`IsResize`/etc. onto `MPORTLETTYPE`). `NULL`/empty means "no
  restriction" — every Portlet Type with no opinion on this keeps working unchanged.
- **Deliberately declarative-only — there is no server-side hard gate.** `GB5Framework` (where
  `PortletBLL` lives) cannot depend on `GB5Solution/Analytics` (where a `BIViewId`'s own
  `WidgetType` would need to be looked up) without inverting this codebase's foundational
  dependency direction — Framework is meant to be depended *on*, never to depend *on* a Solution
  module. The Portlet config screen (`portlet.component.ts`) reads this column to filter its own
  Widget Type and BI View pickers client-side; nothing rejects an out-of-list `WidgetType` at save
  time today.

**Status**: Column live-verified on GB5DEMO (confirmed present and returning `null` for every
existing Portlet Type via a real `GetSelectListPortletType` call, exactly the "no restriction"
default the design intends). FE filtering logic (`visibleWidgetTypeOptions()`/`visibleBIViews()`)
is `tsc --noEmit` clean; not separately browser-click-through-verified.

---

## 9. Dashboard Portlet Integration

**For End Users**: Once an admin has created a BI-backed widget, it shows up on a dashboard exactly
like any other portlet — Chart, KPI Card, Single Card, etc. Nothing BI-specific to learn here.

**For Client Admin / ImpTeam**: "Analysis" is a real, permanent Portlet Type — it appears in the
Portlet Type dropdown on both the dashboard's own "add portlet" flow and the Portlet config screen.
Placing an already-configured widget onto a dashboard page uses the exact same existing flow as
any other portlet type.

**For Developers**:
- `features/gbdashboard/service/widgetcomponentmap.ts` maps `PortletTypeName === 'Analysis'` to
  `Gb5WidgetPortletComponent` — unchanged by the BI rebrand.
- `Gb5WidgetPortletComponent.ngOnInit` is **BIViewId-first**: if `PortletData.BIViewId > 0`, it
  fetches and deserializes that view's own `BIDefinitionJson`/`AdapterConfigJson` (including a
  saved pivot layout) and renders from that. If `BIViewId` is absent, it falls back to the
  original flat `PortletUrl`/`PortletChartXAxis`/`PortletChartYAxis` reconstruction, unchanged —
  every portlet saved before BI Views existed keeps working exactly as before, with no backfill
  required.

**Status**: Code-complete, committed. The BIViewId-first read path and its legacy fallback are
`tsc --noEmit` clean and structurally reviewed; a real `SavePortlet`/dashboard-render round-trip
specifically exercising a real `BIViewId` value has **not** been live-tested this session
(deliberately not attempted against GB5DEMO's real, dashboard-visible `MPORTLET` rows — unlike the
disposable Analytics pilot data used elsewhere in this catalog, a fabricated Portlet row is real,
user-visible dashboard content, judged too risky to fabricate on a shared demo tenant without a
specific need). Earlier, pre-BI-View phases of this same portlet type were separately
API-verified end-to-end (`SavePortlet`/`GetPortletListWithCriteria` round-trip).

---

## 10. Portlet Config Screen

**For End Users**: Not for you — this is the admin screen used to set up a widget you'll later see
on a dashboard.

**For Client Admin / ImpTeam**: This is the actual answer to "how do I get a BI-backed widget onto
a dashboard without asking a developer." See
[Analytics_AdminConfigGuide.md](Analytics_AdminConfigGuide.md) for the exact click-through:
pick a Portlet Type (Analysis), pick a catalog, pick or author a BI View of any widget type
(including a full pivot table), review field mappings if the catalog came from an existing report,
watch a live preview render, click Save.

**For Developers**:
- `projects/framework/master/portlet/portlet/portlet.component.ts` (+`.html`/`.scss`) — extended
  well beyond its original single Dimension/Measure dropdown pair: a BI View picker, a
  multi-dimension/multi-measure BI View authoring card (checkboxes, not a single select), a
  Widget Type selector filtered by the selected Portlet Type's `SupportedWidgetTypes` (entry 8), a
  Field Mapping Review table (`[(ngModel)]`-bound editable rows, `FormsModule` added alongside the
  existing `ReactiveFormsModule` for this), and a design-mode pivot preview wired to a
  "Save current layout" action.
- `effectivePreviewDefinition()`/`onLayoutSave()` cover the case the original screen never
  handled: previewing and saving a layout change against an *existing*, picked BI View (as opposed
  to the legacy scalar Dimension/Measure pair) — this closed a real gap found while building the
  pivot-authoring flow (picking an existing view previously rendered no preview at all).
- Deliberately a plain reactive form, not the `GBBaseFormGroup`/JSON-`formjson`-driven pattern the
  other `framework/master/*` screens use — the picker choices here are too dynamic (dependent on
  which catalog/view is selected) for that pattern's fixed-picklist assumption.

**Status**: The pre-BI-View version of this screen (Portlet Type → Dataset → Dimension → Measure →
preview → save) was live-verified end-to-end via an automated browser driver against real GB5DEMO
data, including finding and fixing two real bugs (a `computed()`-on-plain-form-values bug that hid
the whole config section permanently, and a `[value]`-vs-`[ngValue]` numeric-stringification bug —
both are exactly the kind of defect neither `tsc --noEmit` nor a code read alone would catch). The
BI View authoring, field-mapping review, and pivot-layout-save additions built on top of that
screen are `tsc --noEmit` clean and structurally reviewed, but have **not** had their own fresh
browser click-through this session — see entry 5's Status for the same underlying dev-server
blocker. Given this screen's own history of DOM-only-catchable bugs, treat the newer additions as
a real, live-browser-verification priority the next time this environment's dev server is usable.
