From 41addc61a65640901244db4992e0e6a3440693a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 09:24:31 +0000 Subject: [PATCH] docs(guides,tooling): clear four .md pages off the doc-snippet ledger (#5174 batch 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walks `content/docs/guide/plugins.md`, `content/docs/guide/building-crud-app.md`, `content/docs/rfcs/0001-clipboard-paste.md` and `content/docs/guide/architecture.md` off `UNGATED_DOCS` — 89 diagnostics, ledger 53 -> 49 entries, covered documents 169 -> 173. Each page reached zero the two honest ways only: a block that should compile made self-contained against the built `dist/` (8 blocks now compile, 4 of them after an edit), or a `FRAGMENT_MARKER` with a written measured reason for one that genuinely cannot (34 blocks). Nothing about the gate's strictness moved — the whole mechanism half of the script is byte-identical to `main`. Three real documented-API defects surfaced and were fixed rather than declared: `building-crud-app`'s REST adapter passed `QueryParams['$orderby']` (a four-shape union) straight into `URLSearchParams.set`, which takes a string; its `TaskDetail` component used `SchemaRenderer` with no import of its own; and `architecture`'s "Type Safety" section, marked `// ✅ Type-checked`, set `ButtonSchema.onClick` to the string `'handleClick'` where the declared type is `() => void | Promise`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe --- content/docs/guide/architecture.md | 31 ++++++++++-- content/docs/guide/building-crud-app.md | 14 +++++- content/docs/guide/plugins.md | 10 ++++ content/docs/rfcs/0001-clipboard-paste.md | 7 +++ scripts/check-doc-snippet-types.mjs | 61 ++++++++++------------- 5 files changed, 85 insertions(+), 38 deletions(-) diff --git a/content/docs/guide/architecture.md b/content/docs/guide/architecture.md index 105722ffce..1c43c31d7b 100644 --- a/content/docs/guide/architecture.md +++ b/content/docs/guide/architecture.md @@ -140,10 +140,21 @@ The `SchemaRenderer` component: ```tsx import { SchemaRenderer } from '@object-ui/react' +import type { BaseSchema } from '@object-ui/types' + +// The schema from step 1, as the object the renderer receives. +const schema: BaseSchema = { + type: 'card', + title: 'Welcome', + body: { + type: 'text', + value: 'Hello, ${user.name}!', + }, +} function App() { - const data = { user: { name: "Alice" } } - + const data = { user: { name: 'Alice' } } + return } ``` @@ -152,6 +163,7 @@ function App() { The registry maps type strings to React components: + ```typescript // During app initialization ComponentRegistry.register('card', CardComponent) @@ -165,6 +177,7 @@ const Component = ComponentRegistry.get('card') // → CardComponent The registered component renders with evaluated props: + ```tsx @@ -179,6 +192,7 @@ ObjectUI uses two registry systems for extensibility: Maps schema types to React components: + ```tsx import { ComponentRegistry } from '@object-ui/core' @@ -197,6 +211,7 @@ ComponentRegistry.register('my-widget', MyWidgetComponent, { Maps field types to input components: + ```tsx import { registerFieldRenderer } from '@object-ui/fields' @@ -274,6 +289,7 @@ ObjectUI uses **Tailwind CSS** exclusively for styling: All component variants use `cva` for type-safe variants: + ```tsx import { cva } from 'class-variance-authority' @@ -299,6 +315,7 @@ const buttonVariants = cva( Use `cn()` helper (tailwind-merge + clsx) for class overrides: + ```tsx import { cn } from '@/lib/utils' @@ -319,11 +336,15 @@ ObjectUI is built with **TypeScript** in strict mode: ```typescript import type { ComponentSchema, ButtonSchema } from '@object-ui/types' +function handleClick() { + // ... +} + const schema: ButtonSchema = { type: 'button', text: 'Click me', variant: 'default', // ✅ Type-checked - onClick: 'handleClick' + onClick: handleClick, // ✅ a handler, not its name — onClick is () => void | Promise } ``` @@ -345,6 +366,7 @@ Heavy dependencies only go in plugins: Don't import components directly - use registries: + ```tsx // ❌ Bad import { MyGrid } from './MyGrid' @@ -359,6 +381,7 @@ ComponentRegistry.register('my-grid', MyGrid) Never use inline styles or CSS-in-JS: + ```tsx // ❌ Bad
@@ -371,6 +394,7 @@ Never use inline styles or CSS-in-JS: Use expressions for dynamic content: + ```tsx // ❌ Bad - hardcoded { type: 'text', value: 'Hello, John!' } @@ -389,6 +413,7 @@ When creating a plugin: 4. Add documentation in `content/docs/plugins/` 5. Add to plugins meta.json + ```typescript // packages/plugin-mywidget/src/index.tsx import { ComponentRegistry } from '@object-ui/core' diff --git a/content/docs/guide/building-crud-app.md b/content/docs/guide/building-crud-app.md index f86e164ac6..26f0287045 100644 --- a/content/docs/guide/building-crud-app.md +++ b/content/docs/guide/building-crud-app.md @@ -36,6 +36,7 @@ pnpm add -D tailwindcss @tailwindcss/vite Add Tailwind to your `vite.config.ts`: + ```ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; @@ -163,7 +164,10 @@ export class RestDataSource implements DataSource { const query = new URLSearchParams(); if (params?.$top) query.set('$top', String(params.$top)); if (params?.$skip) query.set('$skip', String(params.$skip)); - if (params?.$orderby) query.set('$orderby', params.$orderby); + // `$orderby` is a union — an OData clause string, a map, or an array of + // fields. This backend speaks the string form, so narrow to it rather + // than stringifying a shape the server cannot parse. + if (typeof params?.$orderby === 'string') query.set('$orderby', params.$orderby); if (params?.$search) query.set('$search', params.$search); const res = await fetch(`${this.baseUrl}/${resource}?${query}`); const data = await res.json(); @@ -208,6 +212,7 @@ Wire everything together in `src/App.tsx`. `SchemaRendererProvider` injects the data source once, and every `SchemaRenderer` beneath it renders its schema against that one adapter: + ```tsx import './setup'; import { SchemaRenderer, SchemaRendererProvider } from '@object-ui/react'; @@ -259,6 +264,7 @@ resolved** panel naming itself and the object it was about to read. ObjectUI generates forms directly from your schema. Extend `App.tsx` with form state: + ```tsx const [showForm, setShowForm] = useState(false); const [editId, setEditId] = useState(null); @@ -269,6 +275,7 @@ Add a "New Task" button and handle row clicks to open the edit form: Both of these render inside the `SchemaRendererProvider` from Step 5, so neither carries a data source of its own: + ```tsx ```tsx const [activeView, setActiveView] = useState('all'); @@ -352,6 +360,8 @@ server decides. Create a detail page that renders a single record with all its fields: ```tsx +import { SchemaRenderer } from '@object-ui/react'; + function TaskDetail({ taskId, onBack }: { taskId: string; onBack: () => void }) { return (
@@ -390,6 +400,7 @@ Use this component in your main app with simple routing state, or integrate with **Environment config** — Keep your API URL configurable: + ```ts const dataSource = new RestDataSource( import.meta.env.VITE_API_URL || 'http://localhost:3000/api' @@ -402,6 +413,7 @@ const dataSource = new RestDataSource( **Authentication** — Extend `RestDataSource` to inject auth headers: + ```ts class AuthenticatedDataSource extends RestDataSource { constructor(baseUrl: string, private getToken: () => string) { diff --git a/content/docs/guide/plugins.md b/content/docs/guide/plugins.md index 6aca70bc7e..716d1ed79b 100644 --- a/content/docs/guide/plugins.md +++ b/content/docs/guide/plugins.md @@ -203,6 +203,7 @@ Kanban board component with drag-and-drop powered by @dnd-kit. Plugins use React's `lazy()` and `Suspense` to load heavy dependencies on-demand: + ```typescript // The plugin structure import React, { Suspense } from 'react' @@ -248,6 +249,7 @@ Without lazy loading, all this code would be in your main bundle! Plugins automatically register their components when imported: + ```typescript // In the plugin's index.tsx import { ComponentRegistry } from '@object-ui/core' @@ -284,6 +286,7 @@ cd packages/plugin-myfeature ### 2. Create Heavy Implementation + ```typescript // src/MyFeatureImpl.tsx import HeavyLibrary from 'heavy-library' @@ -295,6 +298,7 @@ export default function MyFeatureImpl(props) { ### 3. Create Lazy Wrapper + ```typescript // src/index.tsx import React, { Suspense } from 'react' @@ -334,6 +338,7 @@ export interface MyFeatureSchema extends BaseSchema { ### 5. Configure Build + ```typescript // vite.config.ts import { defineConfig } from 'vite' @@ -420,6 +425,7 @@ Heavy imports go in the `*Impl.tsx` file. Always show a meaningful skeleton while loading: + ```typescript @@ -432,6 +438,7 @@ Always show a meaningful skeleton while loading: Make your plugin type-safe: + ```typescript export type { MyFeatureSchema } from './types' ``` @@ -474,6 +481,7 @@ ls -lh dist/ Check that you imported it in your app: + ```typescript import '@object-ui/plugin-myfeature' ``` @@ -482,6 +490,7 @@ import '@object-ui/plugin-myfeature' Make sure types are exported: + ```typescript export type { MyFeatureSchema } from '@object-ui/plugin-myfeature' ``` @@ -499,6 +508,7 @@ Check that the implementation is in a separate file: Check that ComponentRegistry.register() is called at the module level: + ```typescript // ✅ Good - runs on import ComponentRegistry.register('my-feature', MyFeatureRenderer) diff --git a/content/docs/rfcs/0001-clipboard-paste.md b/content/docs/rfcs/0001-clipboard-paste.md index d151bb2072..1e918ef185 100644 --- a/content/docs/rfcs/0001-clipboard-paste.md +++ b/content/docs/rfcs/0001-clipboard-paste.md @@ -162,6 +162,7 @@ Key rules: ### 5.1 Parser (`@object-ui/core/clipboard`) + ```ts export interface ParsedClipboard { /** 2D string matrix, rows × cells, never null */ @@ -188,6 +189,7 @@ Parser handles: ### 5.2 Coercer (`@object-ui/core/clipboard`) + ```ts export type CoercerType = | 'text' | 'number' | 'integer' | 'currency' | 'percent' @@ -240,6 +242,7 @@ Coercion details per type (v1): ### 5.3 React Hook (`@object-ui/fields/clipboard`) + ```ts export interface UsePasteToGridOptions { /** Columns currently visible / pasteable, in visual order */ @@ -287,6 +290,7 @@ export function usePasteToGrid(opts: UsePasteToGridOptions): UsePasteToGridResul ### 5.4 Preview dialog component + ```tsx ```tsx ```tsx const { onPaste, previewDialog } = usePasteToGrid({ columns: coercersFromObjectSchema(schema), @@ -410,6 +416,7 @@ return ( ### 7.2 EditableGridField (child, staged) + ```tsx const { onPaste, previewDialog } = usePasteToGrid({ columns: coercersFromGridFieldColumns(field.columns), diff --git a/scripts/check-doc-snippet-types.mjs b/scripts/check-doc-snippet-types.mjs index b9d6c9e8e6..057cd7db93 100644 --- a/scripts/check-doc-snippet-types.mjs +++ b/scripts/check-doc-snippet-types.mjs @@ -227,7 +227,7 @@ const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']); * Documents whose snippets are NOT compiled, each with the reason. The default * is covered; this list is the debt, by name, and it can only shrink. * - * ⚠️ 9 of these entries are `.md` pages under `content/docs` that objectui#5174 + * ⚠️ 5 of these entries are `.md` pages under `content/docs` that objectui#5174 * made visible: the collector now reads `.md`, and an entry with a measured reason * is what a page that cannot pass yet is owed. They are DISCLOSED debt, not new * debt — every one of them was equally unverified before, just unnamed. Their @@ -235,15 +235,32 @@ const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']); * package does not export is called out by name, because that is the * reader-visible half (objectui#5160). * - * That count was 19 until objectui#5174's first triage batch, which walked TEN of - * them OFF this list rather than re-wording their reasons: `api/schema-reference`, - * `plugins/index`, and the `guide/` pages `architecture-overview`, `deployment`, - * `expressions`, `notifications`, `public-forms`, `schema-overview`, - * `troubleshooting` and `user-state-persistence`. The direction on that card is - * entries LEAVING. Each page reached zero the honest two ways — a block that - * should compile was made self-contained against the built `dist/`, and a block - * that genuinely cannot compile got a `FRAGMENT_MARKER` declaration with a written - * reason. Nothing about this gate's strictness moved to get them there. + * That count was 19 until objectui#5174's triage batches, which walk pages OFF this + * list rather than re-wording their reasons. The direction on that card is entries + * LEAVING. Batch 1 took ten: `api/schema-reference`, `plugins/index`, and the + * `guide/` pages `architecture-overview`, `deployment`, `expressions`, + * `notifications`, `public-forms`, `schema-overview`, `troubleshooting` and + * `user-state-persistence`. Batch 2 took four more — `guide/plugins`, + * `guide/building-crud-app`, `rfcs/0001-clipboard-paste` and `guide/architecture` + * — clearing 89 diagnostics. Each page reached zero the honest two ways — a block + * that should compile was made self-contained against the built `dist/`, and a + * block that genuinely cannot compile got a `FRAGMENT_MARKER` declaration with a + * written reason. Nothing about this gate's strictness moved to get them there. + * + * Batch 2's two routes in proportion, because the ratio is the reviewable part: it + * brought 42 blocks under the gate — 34 declared fragments and 8 that compile, of + * which 4 already compiled untouched and 4 were edited to. Three of those four + * edits were genuine documented-API defects the ledger had been hiding, and they + * are why a page like `guide/architecture` was worth covering rather than + * declaring wholesale: `building-crud-app`'s REST adapter + * passed `QueryParams['$orderby']` — a four-shape union — straight into + * `URLSearchParams.set`, which takes a string; its `TaskDetail` component used + * `SchemaRenderer` with no import of its own; and `guide/architecture`'s section + * titled "Type Safety", marked `// ✅ Type-checked`, set `ButtonSchema.onClick` to + * the STRING `'handleClick'` where the declared type is `() => void | Promise`. + * The pages that are mostly fragments are mostly fragments for a stated reason: + * `guide/plugins` and the clipboard-paste RFC document packages the reader is being + * taught to create, and an RFC's signature excerpts have no bodies by design. * * objectui#5343 then read that list back and cleared it for the getting-started * pages: no entry for `content/docs/guide/**` or for @@ -271,22 +288,6 @@ const TS_FENCE_LANGUAGES = new Set(['ts', 'tsx', 'typescript']); * @type {Record} */ const UNGATED_DOCS = { - 'content/docs/guide/architecture.md': - '8 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; ' + - '13 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + - 'page never defines; 3 unresolved-module diagnostic(s); plus TS2322x1 — candidate real ' + - 'defects, un-triaged', - 'content/docs/guide/building-crud-app.md': - '1 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; ' + - '20 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + - 'page never defines; 4 unresolved-module diagnostic(s); plus TS2339x1 TS2345x1 TS2882x1 — ' + - 'candidate real defects, un-triaged. This entry read TS2305x2 TS2554x1 TS2724x1 until ' + - 'objectui#5343: `registerAllComponents` (@object-ui/components) and the `ObjectSchema` / ' + - '`Field` builder pair (@object-ui/types) were fabricated. Registration is now what LOADING ' + - 'the packages does (`initializeComponents()` plus the side-effect `@object-ui/fields` ' + - 'import), which also retired the `registerAllFields(Registry)` arity error, and the object ' + - 'metadata is the plain document a data source serves, with its `fields` record typed ' + - '`Record`', 'content/docs/guide/component-registry.md': '3 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; ' + '50 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + @@ -317,10 +318,6 @@ const UNGATED_DOCS = { '10 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + 'page never defines; 6 unresolved-module diagnostic(s); plus TS2339x5 TS2882x1 TS7006x3 — ' + 'candidate real defects, un-triaged', - 'content/docs/guide/plugins.md': - '10 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + - 'page never defines; 6 unresolved-module diagnostic(s); plus TS2882x1 TS7006x3 — candidate ' + - 'real defects, un-triaged', 'content/docs/guide/schema-rendering.md': '8 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; ' + '10 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + @@ -355,10 +352,6 @@ const UNGATED_DOCS = { '43 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; 5 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines', 'content/docs/plugins/plugin-timeline.mdx': '1 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies', - 'content/docs/rfcs/0001-clipboard-paste.md': - '7 parse diagnostic(s) — blocks fenced `ts` that are bare object literals or elided bodies; ' + - '11 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the ' + - 'page never defines; plus TS18004x1 TS2391x3 TS7006x1 — candidate real defects, un-triaged', 'content/docs/utilities/create-plugin.mdx': '1 undefined-name diagnostic(s) — blocks continue an earlier block, or use ambient names the page never defines; 1 unresolved-module diagnostic(s)', 'content/docs/utilities/data-objectstack.mdx':