@ai-matrx/detail

中文文档
0.1.178发布于 2026/10/28MITTypeScriptESM + CJS近 30 天下载 4.8万

THE Detail primitive: ONE registration per record type — header, source health strip, fields, associations, history — wrapped once so it shows as a window (the default), a docked side panel, or a page, with the same content in all three. Owns the presenta

原始使用文档
文档目录7 节
  1. Adoption status
  2. The contract — one registration per record type
  3. Binding the ports
  4. Deep links
  5. Keyboard
  6. Testing a host binding
  7. What is deliberately NOT here

@ai-matrx/detail — the Detail primitive

Arman, 2026-09-17: "a core component that then shows up as a Page, a flexible drawer, and a window panel. The default is the window. … you just make a core component with some core rules, then wrap it in this reusable thing from the package, which then instantly gives you all 3 for free."

ONE registration per record type → three presentations for free, with the same content in all three. Keyboard: Escape closes, the arrows move between records ([ / ] are aliases), and Cmd/Ctrl+Enter saves.

PresentationWhat it isWhat the host binds
window (the default)Floating, draggable, minimizable; a bottom sheet on phonesshells.Window — the host's window manager
dockedA resizable side panel docked to the right edge, no backdrop; a bottom sheet on phonesshells.Docked — the host's side-panel surface
pageA full route body under the app header — the only presentation that changes the URL; offers the window backshells.Page — the host's route header + scroll body

Champions: Notion (side peek / center peek / full page, per-database "open pages in") and Linear (peek, keyboard-first). Bones matched, skin ours.

pnpm add @ai-matrx/detail        # every Matrx repo declares it "latest" (THE LATEST LAW)

Adoption status

matrx-frontend installs this package from npm at latest (adopted 2026-09-30); its former in-repo copy and the byte-parity guard that kept the two even are deleted. A fix to the primitive is made here, released, and reaches the frontend by version. The not-found state renders design-system's ErrorActions slot, so a host's error actions (the frontend's Alchemy menu) appear there without a host-specific body.

EntryShips"use client"
@ai-matrx/detailthe record-type contract, the presentation setting shape and its per-type override, the list-context trim with the final-URL byte budget, the deep-link spelling of every presentation, the row → fields formatterno — pure, legal in a Server Component
@ai-matrx/detail/reactthe ports and provider, the core, the keyboard model, the loader and the health resolver, the one opener, the header halves, the body, the setting pane, the three presentations, the header geometry constantsyes
@ai-matrx/detail/testingthe stub-port seat: makePortsWith(fn), mount, instance, clickByLabel, three stub shells, FILE_TYPE — the seat the package proves itself with, for a host to prove its binding withyes

The contract — one registration per record type

import type { DetailRecordType } from "@ai-matrx/detail";

const task: DetailRecordType = {
  type: "task", label: "Task", icon: TaskIcon, accent: null,
  entityToken: "task",          // doors · associations · history key off this; null = none
  load: (id, signal) => …,      // null = no single source → renders from the seed, and says so
  title: (row, seed) => …,
  fields: (row) => […],
  health?: (row, ctx) => …,     // the source health strip for a SYNCED record (sync or async)
  associationTokens?: […],      // null hides the section; omitted → the host default minus self
  history?: true,               // default: when entityToken is set
  extraSections?: (row, ctx) => […],
  Frame?: ({ ctx, children }) => …,   // wraps the body: surface runtime, right-click menu, …
};

Fixed sections, in order: source health strip (synced records only) · about · fields · associations (AssociationCardGrid under a PrimaryEntityProvider, from @ai-matrx/associations) · history (row versions) · extras · the "how details open" pane. A section that does not apply is absent, never an empty box.

The one opener: useOpenDetail()({ type, id, presentation?, seed?, list? }). Without presentation it asks the host's resolvePresentation(type) — the person's ui.detail.default_presentation, with ui.detail.presentation_by_type read first for that type. A resolver that raises is announced (the host's notify.error, naming the key) and the window opens. Nothing navigates away except page.

Binding the ports

Everything app-shaped arrives through DetailHostPorts (@ai-matrx/detail/react). Providers nest and merge: bind the light ports ONCE at boot so useOpenDetail works everywhere, and let each presentation's lazily loaded entry add its own shell and the record-type map right above the presentation, so the window shell never reaches a boot bundle. A missing port throws, naming itself.

PortWhat the host injectsRequired
resolveType(type)THE record-type map — one registry, never a secondper presentation entry
usePresentationSetting(type) · resolvePresentation(type) · warmPresentation(type)the settings ladder's answer for ui.detail.default_presentation and ui.detail.presentation_by_type (React face, awaited face, cache warmer)yes
reReadPresentation(type)a FRESH ladder read after a write, naming the rung that answers nowoptional
savePresentation({ presentation, forType, clear? })the ONE writer of the setting and its per-type map entry; a refusal comes back as { ok: false, reason }optional — absent ⇒ the pane is absent
open · closethe in-place presentations (window / docked) on the host's overlay systemyes
navigate.pageHref · toPage · back · canGoBack · toRecordHomethe page route; canGoBack answers "did THIS tab push the page" and toRecordHome replaces the page with the record's home — never a raw back() into an empty historyyes
shells.Window · Docked · Pagethe chrome; the package fills title, titleNode, actions, children, onClose (+ onBack for the page, + list for the window's deep link)per presentation entry
doors.RecordDoors · RefCell · tokenFromColumnName · isUuidValue · hasDoorthe host's entity doors (open / new tab / peek); hasDoor decides whether the absent state may claim "the controls above still open it"yes
associations.defaultTokens · canAnchorwhich tokens the associations section shows and which may anchor oneyes
history.list(token, id, signal)row versionsyes
notify.error · success · copyTextthe host's toast and clipboardyes
reconnectSource(ref, source)the same Reconnect the connector rows offer, for a synced record's stripoptional
remedy.typeMapwhere the developer goes to register a type — the console remedy names itoptional

The knobs the package READS THROUGH PORTS and never resolves itself: ui.detail.default_presentation (enum window|docked|page, organization → user), ui.detail.presentation_by_type (json map, same ladder), ui.detail.list_context_max_ids (organization; the host resolves it and passes the number into encodeListQuery / detailListToUrlArgs, which clamp it to DETAIL_LIST_CONTEXT_MAX_IDS_CEILING). Their rows are seeded by matrx-frontend's migrations.

Window and docked deep links ride the host's ONE panel-URL mechanism under the type key detail, instance type.id, arg as: ?panels=detail:task.<uuid>:as-window / :as-docked, carrying the list the record was opened from (l / i / lt). The page is the host's route (/detail/<type>/<id> in matrx-frontend) with ?l=type.id,…&i=<n>&lt=<total>. Both spellings are cut by ONE trim (trimListContext) against ONE budget — 8,000 characters of the FINAL serialized URL, with what the rest of the address costs reserved — and the record says when the list was trimmed. . : , inside a type token or an id are escaped, so an external id such as sc-domain:example.com round-trips as the same record.

Keyboard

useDetailKeyboard, bound on every slot the presentation fills (header and body): Escape closes from any control except an open menu / select / combobox and a text field with uncommitted edits; ArrowUp/Down/Left/Right move between records only when the body is not the scroll target, [ / ] always move; Cmd/Ctrl+Enter saves what a section registered — and says there is nothing to save when nothing did.

Testing a host binding

import { makePortsWith, mount, instance, StubPageShell } from "@ai-matrx/detail/testing";
const makePorts = makePortsWith(jest.fn);   // or vi.fn — the seat takes the runner's factory
const ports = makePorts({ shells: { Page: StubPageShell } });
const m = mount(<DetailPagePresentation data={instance()} />, ports);

Every port is a mock, so a test asserts what the core ASKED THE HOST TO DO — which is where every recorded defect of this primitive lived.

What is deliberately NOT here

  • The host's window manager, side panel and route header — they are shells, bound as ports.
  • The settings ladder, the toast, the clipboard, the router, the entity registry — ports.
  • An icon library: the seventeen glyphs the primitive draws are inlined SVGs (Lucide-derived, ISC). A record type's own icon is host data on its registration.
  • Tailwind CSS generation: the components are styled with utility classes. Register the built package with the consumer's Tailwind scan (@source "../node_modules/@ai-matrx/detail/dist"; in matrx-frontend's globals.css, the design-system precedent).

Implementation truth, file map and change history: FEATURE.md beside this file. Packaging law: common-docs/policies/typescript-package-standard.md.

版本与发布记录

@ai-matrx/detail 共发布 178 个版本,首次发布于 2026/09/20;过去 12 个月发布了 178 个版本。

版本发布日期距今
0.1.178latest2026/10/289 天前
0.1.1772026/10/2412 天前
0.1.1762026/10/2413 天前
0.1.1752026/10/2313 天前
0.1.1742026/10/2314 天前
0.1.1732026/10/2214 天前

关于 @ai-matrx/detail 的常见问题

@ai-matrx/detail 是什么?

THE Detail primitive: ONE registration per record type — header, source health strip, fields, associations, history — wrapped once so it shows as a window (the default), a docked side panel, or a page, with the same content in all three. Owns the presenta

如何安装 @ai-matrx/detail?

在项目目录运行 npm install @ai-matrx/detail,或使用 pnpm add / yarn add / bun add。

@ai-matrx/detail 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ai-matrx/detail 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ai-matrx/detail 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@ai-matrx/detail 可以商用吗?

@ai-matrx/detail 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ai-matrx/detail 的下载量有多少?

根据 npm 官方下载统计,@ai-matrx/detail 在 2026/10/05 – 2026/11/03 这 30 天内被下载 48,431 次,日均约 1,614 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ai-matrx/detail 还在维护吗?

最新版本 0.1.178 发布于 2026/10/28(9 天前);至今共发布 178 个版本,其中过去 12 个月发布了 178 个;首次发布于 2026/09/20。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。