@loykin/designkit 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,435 @@
1
+ # Managed Table Pattern Contract for AI
2
+
3
+ This is the normative implementation contract for a tabbed administrative table built with `@loykin/designkit`, `@loykin/gridkit`, TanStack Query, and React Router. Copy this entire document into an AI task before asking it to create or revise the page.
4
+
5
+ The Playground's **Guides / Resource Management / Managed Table** screen is the executable end-to-end reference. Its records are sample data; the pattern is defined by the table responsibilities and template composition below, not by that data domain. If an implementation differs from this contract, revise the implementation rather than inventing a local layout.
6
+
7
+ ## Pattern identity and reference registry
8
+
9
+ - Pattern ID: `managed-table`
10
+ - Primary list template: `DataBodyTemplate`
11
+ - Full-detail template when required: `DetailBodyTemplate`
12
+ - Multi-step create/edit template when required: `FormWizardBodyTemplate`
13
+ - Resource boundary: `DataBodyTemplate.Resource`
14
+ - Executable pattern route: `/sidebar/databody-managed-table-guide` and `/header/databody-managed-table-guide`
15
+ - Executable pattern source: `playground/src/templates/demos/databody/DataBodyManagedTableGuide.tsx`
16
+
17
+ | Status | Playground reference | Role in this pattern |
18
+ | -------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
19
+ | Executable pattern | `Guides / Resource Management / Managed Table` | Complete list, tabs, queries, create route, form, pagination, and concise detail Sheet |
20
+ | Supporting reference | `DataBodyTemplate / Table / Standard` | Base GridKit table composition and table sizing |
21
+ | Supporting contract | `Guides / Forms / Stacked Form` | Create/edit form spacing, modular Groups, and action alignment |
22
+ | Supporting reference | `DetailBodyTemplate / Detail / Record` | Full-page destination when detail outgrows a Sheet |
23
+ | Supporting reference | `FormWizardBodyTemplate / Wizard` | Multi-step destination when create/edit outgrows a stacked form |
24
+ | Counterexample | `DashboardBodyTemplate / Dashboard` | Monitoring panels are not an administrative table |
25
+ | Counterexample | `BrowseBodyTemplate / Browse` | Consumer discovery is not an administrative resource list |
26
+
27
+ Only the entry marked **Executable pattern** implements this pattern end to end. Supporting references define the correct destination or visual sub-composition; they are not additional domain implementations of Managed Table.
28
+
29
+ ## Pattern composition map
30
+
31
+ | Responsibility | Primary API | Playground example to inspect |
32
+ | -------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------- |
33
+ | Collection list, tabs, search, filters, pagination | `DataBodyTemplate` + `DataBodyTemplate.Resource` | `Guides / Resource Management / Managed Table` |
34
+ | Base table behavior | GridKit `DataGrid` | `DataBodyTemplate / Table / Standard` |
35
+ | Simple create/edit route | stacked `DataBodyTemplate.Group` | `Guides / Forms / Stacked Form` |
36
+ | Concise read-only inspection | `Sheet` | `Managed Table` row detail |
37
+ | Complex full-page detail route | `DetailBodyTemplate` | `DetailBodyTemplate / Detail / Record` |
38
+ | Multi-step create/edit route | `FormWizardBodyTemplate` | `FormWizardBodyTemplate / Wizard` |
39
+ | Destructive confirmation | `AlertDialog` | UI primitive contract |
40
+
41
+ ## Pattern applicability
42
+
43
+ Use this pattern when the screen manages a collection of repeated server records with most of these responsibilities: search or filters, resource-scoped actions, pagination, background refresh, and row inspection. The record name does not select the pattern; the screen responsibilities do.
44
+
45
+ ## When not to use this pattern
46
+
47
+ | Screen shape | Prefer | Reason |
48
+ | ------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
49
+ | Metrics and monitoring panels | `DashboardBodyTemplate` | The primary unit is a panel, not a record collection |
50
+ | One entity with many editable settings sections | `DataBodyTemplate.Section` or grouped form page | There is no collection resource boundary |
51
+ | Consumer catalog or faceted discovery | `BrowseBodyTemplate` | Browsing and comparison dominate administration |
52
+ | Multi-step provisioning | `FormWizardBodyTemplate` | Ordered steps and validation dominate the flow |
53
+ | Long or multi-section record detail | `DetailBodyTemplate` | The entity itself is the page |
54
+ | Persistent master-detail workspace | `ListDetailBodyTemplate` or `WorkbenchBodyTemplate` | List and detail are simultaneously primary panes |
55
+ | Small static list with no query, filters, or pagination | Plain `DataBodyTemplate` content | `Resource` and TanStack Query add unnecessary structure |
56
+
57
+ ## Pattern selection questions
58
+
59
+ Choose `managed-table` when the answers are mostly yes:
60
+
61
+ 1. Is the primary object a collection of repeated records?
62
+ 2. Does the collection own search, filters, pagination, polling, or server state?
63
+ 3. Are actions such as create or export scoped to that collection or active tab?
64
+ 4. Should concise row inspection preserve the list context?
65
+ 5. Can create/edit and complex detail use independent routes?
66
+
67
+ If the first two answers are no, do not use this pattern. This pattern may use different page-level templates on different routes, but each route still renders exactly one page-level template. Never nest those templates inside one another.
68
+
69
+ ## Scope
70
+
71
+ This guide covers three destinations for any managed resource:
72
+
73
+ ```text
74
+ /<resources>
75
+ ├─ select row → concise read-only detail Sheet
76
+ ├─ create action → /<resources>/new create page
77
+ └─ edit action → /<resources>/:resourceId/edit page
78
+ ```
79
+
80
+ It does not define every `DataBodyTemplate` use case. Settings, dashboards, long-form detail pages, and wizards have separate patterns.
81
+
82
+ ## Destination decision
83
+
84
+ | User intent | Default destination | Reason |
85
+ | ------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------- |
86
+ | Browse, search, filter, paginate | Page | Stable, linkable working context |
87
+ | Inspect concise read-only row information | Sheet | Preserves list context |
88
+ | Create or edit an entity | Page with its own URL | Space for validation, permissions, responsive layout, and future fields |
89
+ | Inspect long, editable, multi-section, permission-sensitive, or linkable detail | Page with its own URL | Sheet constraints are no longer appropriate |
90
+ | Confirm a destructive action or collect one narrowly scoped value | Modal | Short, blocking decision |
91
+
92
+ A Sheet is not the default form container. Do not put general create or edit forms in a Sheet merely because the trigger originates in a table.
93
+
94
+ ## Route and page hierarchy
95
+
96
+ Use real routes and browser history. Do not simulate navigation with component state such as `view === 'create'`. The `/users` routes below are concrete examples; substitute the target collection route.
97
+
98
+ ```tsx
99
+ <Routes>
100
+ <Route path="/users" element={<UsersListPage />} />
101
+ <Route path="/users/new" element={<UserCreatePage />} />
102
+ <Route path="/users/:userId/edit" element={<UserEditPage />} />
103
+ </Routes>
104
+ ```
105
+
106
+ Every destination retains its breadcrumb:
107
+
108
+ ```tsx
109
+ // List
110
+ <PageTopBar left={<PageBreadcrumb items={['Data', 'Users']} />} />
111
+
112
+ // Create
113
+ <PageTopBar
114
+ left={
115
+ <PageBreadcrumb
116
+ items={['Data', { label: 'Users', href: '/users' }, 'Add user']}
117
+ />
118
+ }
119
+ />
120
+ ```
121
+
122
+ The collection crumb on create, edit, and full-detail pages must navigate back to the list route.
123
+
124
+ ## List-page hierarchy
125
+
126
+ Use exactly one application shell and one page-level template. Tabs are direct children of the page template. Each tab delegates to an isolated resource component.
127
+
128
+ ```tsx
129
+ function UsersListPage() {
130
+ return (
131
+ <DataBodyTemplate
132
+ topBar={<PageTopBar left={<PageBreadcrumb items={['Data', 'Users']} />} />}
133
+ title="Users"
134
+ description="Manage users, sessions, and account history."
135
+ >
136
+ <DataBodyTemplate.Tab id="users" label="Users">
137
+ <UsersTab />
138
+ </DataBodyTemplate.Tab>
139
+ <DataBodyTemplate.Tab id="sessions" label="Sessions">
140
+ <SessionsTab />
141
+ </DataBodyTemplate.Tab>
142
+ <DataBodyTemplate.Tab id="history" label="History">
143
+ <HistoryTab />
144
+ </DataBodyTemplate.Tab>
145
+ </DataBodyTemplate>
146
+ )
147
+ }
148
+ ```
149
+
150
+ Do not nest another page-level template inside `DataBodyTemplate`. Do not place tab-dependent controls in the page header or top bar.
151
+
152
+ ## Tab and query ownership
153
+
154
+ Each tab must be a separate React component and own all state that affects its resource:
155
+
156
+ - TanStack Query and query key
157
+ - Search and filters
158
+ - Pagination
159
+ - Row selection and concise detail Sheet
160
+ - Mutations that belong to the list itself
161
+
162
+ Query keys include every server-state input:
163
+
164
+ ```tsx
165
+ const [search, setSearch] = useState('')
166
+ const [role, setRole] = useState('all')
167
+ const [page, setPage] = useState(1)
168
+
169
+ const usersQuery = useQuery({
170
+ queryKey: ['users', search, role, page],
171
+ queryFn: () => listUsers({ search, role, page }),
172
+ placeholderData: keepPreviousData,
173
+ refetchInterval: 8_000,
174
+ })
175
+
176
+ const hasData = usersQuery.data !== undefined
177
+ ```
178
+
179
+ Do not call every tab's query in the page parent and select data with an `activeTab` conditional.
180
+
181
+ ## Resource toolbar
182
+
183
+ Search and filters go on the left. Resource actions go on the right. `Add user` remains beneath the Users tab even though it navigates to another page.
184
+
185
+ ```tsx
186
+ <DataBodyTemplate.Resource
187
+ toolbarLeft={
188
+ <>
189
+ <UserSearch value={search} onChange={setSearchAndResetPage} />
190
+ <RoleFilter value={role} onChange={setRoleAndResetPage} />
191
+ </>
192
+ }
193
+ toolbarRight={
194
+ <Button size="sm" onClick={() => navigate('/users/new')}>
195
+ Add user
196
+ </Button>
197
+ }
198
+ >
199
+ {/* DataGrid */}
200
+ </DataBodyTemplate.Resource>
201
+ ```
202
+
203
+ Resource toolbar rules:
204
+
205
+ - Search comes before filters in `toolbarLeft`.
206
+ - Create, export, and view actions belong in `toolbarRight`.
207
+ - Table-toolbar controls are compact: `28px` (`h-7`).
208
+ - Do not place filters in `toolbarRight`.
209
+ - Do not move `Add user` into the page header.
210
+ - Do not add a manual Refresh button when background refetch is sufficient. If explicitly required by the product, keep it in the resource toolbar.
211
+
212
+ ## Async refresh behavior
213
+
214
+ Do not equate `isFetching` with an empty loading state.
215
+
216
+ | Query state | Existing rows | Presentation |
217
+ | ---------------------------------- | ------------- | ------------------------------------------------------- |
218
+ | `isPending && data === undefined` | None | Grid body loading state |
219
+ | `isFetching && data !== undefined` | Preserve | Keep the grid unchanged; no indicator by default |
220
+ | `isError && data !== undefined` | Preserve | Non-destructive stale-data notice |
221
+ | `isError && data === undefined` | None | Error and retry in the resource body |
222
+ | Empty result | None | Grid empty state while retaining toolbar and pagination |
223
+
224
+ During background refresh:
225
+
226
+ - Preserve the `DataGrid`, toolbar, footer, current page, and selected detail.
227
+ - Do not replace the resource group with a spinner or skeleton.
228
+ - Do not derive a React `key` from `isFetching`, query results, or a refresh timestamp.
229
+ - Keep automatic polling silent by default. A repeating `Refreshing` label creates unnecessary motion and toolbar reflow.
230
+ - `Resource.refreshing` is opt-in for an explicit user-initiated refresh or a product where freshness feedback is operationally important. When used, require both `query.isFetching` and existing data.
231
+ - Initial loading with no data belongs to GridKit's `isLoading` skeleton state, not `Resource.refreshing`.
232
+
233
+ After a mutation, invalidate only the affected resource keys. Do not invalidate a broad page key that refetches unrelated tabs.
234
+
235
+ ## Sorting and stable ordering
236
+
237
+ Every table declares an initial order. Do not rely on source-array order or the timing of query responses.
238
+
239
+ - Client-side tables use GridKit `initialSorting`.
240
+ - Server-paginated tables keep `SortingState` in the resource component, include it in the TanStack Query key, pass `manualSorting`, and sort in the query/API before slicing the requested page.
241
+ - Reset to page one when sorting changes.
242
+ - Human labels such as `12 min ago` are display values, not sort keys. Use a timestamp, sequence, or numeric `accessorFn` while rendering the human label from `row.original`.
243
+ - Add a stable tie-breaker such as the record ID when equal primary values are possible.
244
+ - A draggable table is the exception: its persisted manual order is authoritative and column sorting is disabled.
245
+
246
+ ```tsx
247
+ const INITIAL_SORTING: SortingState = [{ id: 'name', desc: false }]
248
+ const [sorting, setSorting] = useState<SortingState>(INITIAL_SORTING)
249
+
250
+ const query = useQuery({
251
+ queryKey: ['users', search, role, page, sorting],
252
+ queryFn: () => listUsers({ search, role, page, sorting }),
253
+ })
254
+
255
+ <DataGrid
256
+ initialSorting={INITIAL_SORTING}
257
+ manualSorting
258
+ onSortingChange={(nextSorting) => {
259
+ setSorting(nextSorting)
260
+ setPage(1)
261
+ }}
262
+ />
263
+ ```
264
+
265
+ Sorting must happen before pagination in `listUsers`. Sorting only the six rows already returned for the current page produces a false order across pages.
266
+
267
+ ## GridKit pagination
268
+
269
+ GridKit owns its pagination UI. Do not build a second paginator and do not pass GridKit pagination through `DataBodyTemplate.Resource.footer`.
270
+
271
+ ```tsx
272
+ <DataGrid
273
+ data={usersQuery.data?.items ?? []}
274
+ columns={userColumns}
275
+ getRowId={(row) => row.id}
276
+ isLoading={usersQuery.isPending && !hasData}
277
+ tableWidthMode="fill-last"
278
+ classNames={{ footer: 'pt-3' }}
279
+ pagination={{
280
+ pageSize: PAGE_SIZE,
281
+ pageIndex: page - 1,
282
+ pageCount: Math.max(1, Math.ceil((usersQuery.data?.total ?? 0) / PAGE_SIZE)),
283
+ onPageChange: (pageIndex) => setPage(pageIndex + 1),
284
+ }}
285
+ footer={(table) => (
286
+ <DataGridPaginationBar
287
+ table={table}
288
+ totalCount={usersQuery.data?.total ?? 0}
289
+ pageSizes={[PAGE_SIZE]}
290
+ />
291
+ )}
292
+ />
293
+ ```
294
+
295
+ Grid rules:
296
+
297
+ - Convert the application's one-based page to GridKit's zero-based `pageIndex` only at the grid boundary.
298
+ - Use the public GridKit `footer` slot and `DataGridPaginationBar`.
299
+ - Use `classNames={{ footer: 'pt-3' }}` for the required `12px` separation from the table.
300
+ - Pagination controls are `28px` high, matching the resource toolbar.
301
+ - Do not target GridKit's internal DOM with global CSS.
302
+
303
+ ## Create and edit form page
304
+
305
+ Create and edit forms follow the canonical `form-workflow` contract in **Guides / Forms / Stacked Form**. Preserve the template-owned page width and padding, and split semantic Groups into named section components.
306
+
307
+ ```tsx
308
+ function UserCreatePage() {
309
+ const navigate = useNavigate()
310
+ const queryClient = useQueryClient()
311
+ const createUser = useMutation({
312
+ mutationFn: createUserRequest,
313
+ onSuccess: async () => {
314
+ await queryClient.invalidateQueries({ queryKey: ['users'] })
315
+ navigate('/users')
316
+ },
317
+ })
318
+
319
+ return (
320
+ <DataBodyTemplate
321
+ topBar={
322
+ <PageTopBar
323
+ left={<PageBreadcrumb items={['Data', { label: 'Users', href: '/users' }, 'Add user']} />}
324
+ />
325
+ }
326
+ title="Add user"
327
+ description="Create a user account."
328
+ >
329
+ <DataBodyTemplate.Group
330
+ layout="stacked"
331
+ title="User information"
332
+ description="Identity and access settings for the new account."
333
+ >
334
+ <form className="space-y-3" onSubmit={handleSubmit}>
335
+ <div className="space-y-1.5">
336
+ <Label htmlFor="user-name" className="text-xs">
337
+ Name
338
+ </Label>
339
+ <Input id="user-name" className="h-8 text-sm" required />
340
+ </div>
341
+ <div className="space-y-1.5">
342
+ <Label htmlFor="user-email" className="text-xs">
343
+ Email
344
+ </Label>
345
+ <Input id="user-email" type="email" className="h-8 text-sm" required />
346
+ </div>
347
+ <div className="space-y-1.5">
348
+ <Label htmlFor="user-role" className="text-xs">
349
+ Role
350
+ </Label>
351
+ <Select defaultValue="Viewer">
352
+ <SelectTrigger id="user-role" className="h-8 text-sm">
353
+ <SelectValue />
354
+ </SelectTrigger>
355
+ <SelectContent>{/* roles */}</SelectContent>
356
+ </Select>
357
+ </div>
358
+ <div className="flex justify-end gap-2">
359
+ <Button
360
+ type="button"
361
+ variant="outline"
362
+ size="sm"
363
+ className="h-8 text-xs"
364
+ onClick={() => navigate('/users')}
365
+ >
366
+ Cancel
367
+ </Button>
368
+ <Button type="submit" size="sm" className="h-8 text-xs">
369
+ Create user
370
+ </Button>
371
+ </div>
372
+ </form>
373
+ </DataBodyTemplate.Group>
374
+ </DataBodyTemplate>
375
+ )
376
+ }
377
+ ```
378
+
379
+ Form rules:
380
+
381
+ - Form controls and buttons are `32px` (`h-8`); this differs from the table toolbar's `28px` controls.
382
+ - Use `space-y-3` for the form and `space-y-1.5` within each field.
383
+ - Use the full content width supplied by the stacked group.
384
+ - Do not add `mx-auto`, an arbitrary `max-w-*`, extra horizontal padding, a nested card, or a second action divider.
385
+ - The form page owns its mutation. After success, invalidate the narrowest affected queries and navigate to the list or created entity.
386
+
387
+ ## Concise detail Sheet
388
+
389
+ Keep the list mounted while showing concise, mostly read-only row information:
390
+
391
+ ```tsx
392
+ <Sheet open={Boolean(selectedUser)} onOpenChange={(open) => !open && clearSelection()}>
393
+ <SheetContent>
394
+ <SheetHeader>
395
+ <SheetTitle>{selectedUser?.name}</SheetTitle>
396
+ <SheetDescription>User details</SheetDescription>
397
+ </SheetHeader>
398
+ {/* concise read-only fields */}
399
+ </SheetContent>
400
+ </Sheet>
401
+ ```
402
+
403
+ If the detail gains editing, multiple sections, complex permissions, a long history, or a need for a shareable URL, replace the Sheet with a detail page. Do not gradually turn the Sheet into a full page inside an overlay.
404
+
405
+ ## Public API boundary
406
+
407
+ Import components only from public entry points:
408
+
409
+ ```tsx
410
+ import { DataBodyTemplate, PageBreadcrumb, PageTopBar } from '@loykin/designkit'
411
+ import { DataGrid, DataGridPaginationBar } from '@loykin/gridkit'
412
+ ```
413
+
414
+ Do not import package-internal source paths. Do not add TanStack Query, React Router, or domain-specific data fetching to DesignKit's core package; those remain application concerns.
415
+
416
+ ## Required acceptance checks
417
+
418
+ - [ ] Exactly one application shell and one page-level template are visible.
419
+ - [ ] The list, `/new`, and edit routes are real URLs, not component-state modes.
420
+ - [ ] List, create, edit, and full-detail pages retain their breadcrumb hierarchy.
421
+ - [ ] Tabs are directly below the page header.
422
+ - [ ] Each tab is an isolated component with its own query and state.
423
+ - [ ] Search and filters are on the left; resource actions are on the right.
424
+ - [ ] No tab-dependent control appears in the page header.
425
+ - [ ] Initial loading uses the GridKit skeleton while background refresh preserves existing rows.
426
+ - [ ] Automatic polling is silent; any visible `refreshing` state is explicitly justified.
427
+ - [ ] GridKit owns controlled pagination and footer rendering.
428
+ - [ ] The GridKit footer has `pt-3` separation.
429
+ - [ ] Table toolbar and pagination controls are `28px` high.
430
+ - [ ] Form controls and buttons are `32px` high.
431
+ - [ ] The stacked form has no arbitrary max-width wrapper or extra action divider.
432
+ - [ ] Create and edit use pages; concise read-only detail uses a Sheet.
433
+ - [ ] Complex detail uses a page.
434
+ - [ ] Only public package entry points are imported.
435
+ - [ ] The experience is visually checked in both Sidebar and Header shells.
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "description": "Versioned implementation guides for @loykin/designkit consumers and coding agents.",
4
+ "guides": [
5
+ {
6
+ "id": "managed-table",
7
+ "title": "Resource Management / Managed Table",
8
+ "summary": "Administrative collection with isolated queries, search, filters, sorting, pagination, CRUD routes, and concise detail inspection.",
9
+ "useWhen": ["admin CRUD", "resource table", "search and filters", "server pagination"],
10
+ "templates": ["DataBodyTemplate", "DetailBodyTemplate", "FormWizardBodyTemplate"],
11
+ "playgroundPaths": [
12
+ "/sidebar/databody-managed-table-guide",
13
+ "/header/databody-managed-table-guide"
14
+ ],
15
+ "contract": "managed-table.md"
16
+ },
17
+ {
18
+ "id": "form-workflow",
19
+ "title": "Forms / Stacked Form",
20
+ "summary": "Canonical stacked page structure and modular section boundaries for create, edit, and settings forms.",
21
+ "useWhen": ["create form", "edit form", "settings form", "multiple form sections"],
22
+ "templates": ["DataBodyTemplate"],
23
+ "playgroundPaths": ["/sidebar/form-workflow-guide", "/header/form-workflow-guide"],
24
+ "contract": "form-workflow.md"
25
+ },
26
+ {
27
+ "id": "publishing-workflow",
28
+ "title": "Publishing / Blog → Article",
29
+ "summary": "Searchable content collection connected to a stable, independently queried article route.",
30
+ "useWhen": ["blog", "journal", "content feed", "article detail"],
31
+ "templates": ["DataBodyTemplate", "DetailBodyTemplate"],
32
+ "playgroundPaths": [
33
+ "/sidebar/publishing-workflow-guide",
34
+ "/header/publishing-workflow-guide"
35
+ ],
36
+ "contract": "publishing-workflow.md"
37
+ },
38
+ {
39
+ "id": "commerce-workflow",
40
+ "title": "Commerce / Catalog → Product",
41
+ "summary": "Filterable catalog connected to a stable product decision and purchase route.",
42
+ "useWhen": ["commerce", "product catalog", "faceted browsing", "product detail"],
43
+ "templates": ["BrowseBodyTemplate", "DetailBodyTemplate"],
44
+ "playgroundPaths": ["/sidebar/commerce-workflow-guide", "/header/commerce-workflow-guide"],
45
+ "contract": "commerce-workflow.md"
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,53 @@
1
+ # Publishing Workflow Contract for AI
2
+
3
+ Use this guide to build a publishing experience where a content collection leads to a stable article URL. The executable reference is **Guides / Publishing / Blog → Article**.
4
+
5
+ ## Identity
6
+
7
+ - Pattern ID: `publishing-workflow`
8
+ - Collection route template: `DataBodyTemplate`
9
+ - Article route template: `DetailBodyTemplate`
10
+ - Server state: TanStack Query
11
+ - Executable source: `playground/src/templates/demos/guides/PublishingWorkflowGuide.tsx`
12
+
13
+ The existing **Blog Feed** and **Article** template demos are visual references only. This Guide is the supported example of how those page shapes connect.
14
+
15
+ ## Route contract
16
+
17
+ ```text
18
+ /journal → searchable, newest-first article collection
19
+ /journal/:articleSlug → linkable article destination
20
+ ```
21
+
22
+ Card selection uses React Router navigation. Browser Back returns to the collection. The article breadcrumb links to the collection route. Do not open a long article in a Sheet or simulate the route with `selectedArticle` component state.
23
+
24
+ ## Query ownership
25
+
26
+ - `PublishingList` owns `['publishing', 'articles']` and collection search/sort state.
27
+ - `PublishingArticle` owns `['publishing', 'article', slug]`.
28
+ - Initial collection loading shows card skeletons through GridKit `isLoading`.
29
+ - Background collection refetch preserves existing cards.
30
+ - Article loading affects only the article route.
31
+
32
+ ## Layout and action rules
33
+
34
+ - Collection-wide actions such as **New article** may appear in the collection page header.
35
+ - Search and category filters belong with the collection, above its cards.
36
+ - Card-local actions stay on the card; selecting the card navigates.
37
+ - Article actions such as Save or Share belong in the article header.
38
+ - Render one page-level template per route. Never put `DetailBodyTemplate` inside `DataBodyTemplate.Body`.
39
+
40
+ ## Deterministic ordering
41
+
42
+ The mock API returns articles newest first and the GridKit collection declares the same initial published-date sort. In a real API, send sort parameters to the server and include them in the query key. Never sort humanized labels such as “2 hours ago”.
43
+
44
+ ## AI reconstruction checklist
45
+
46
+ - [ ] Real collection and article routes
47
+ - [ ] `DataBodyTemplate` collection and `DetailBodyTemplate` article
48
+ - [ ] Independent TanStack Query boundaries
49
+ - [ ] Card click navigates to the article slug
50
+ - [ ] Breadcrumb returns to the collection
51
+ - [ ] Initial loading is local to the destination content
52
+ - [ ] Existing content remains visible during background refetch
53
+ - [ ] No nested page templates and no article Sheet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loykin/designkit",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A React UI component library with theming support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -18,10 +18,20 @@
18
18
  "./styles": {
19
19
  "types": "./dist/styles.d.ts",
20
20
  "default": "./dist/styles.css"
21
- }
21
+ },
22
+ "./guides/manifest.json": "./docs/guides/manifest.json",
23
+ "./guides/*": "./docs/guides/*"
24
+ },
25
+ "bin": {
26
+ "designkit": "./cli/designkit.mjs"
27
+ },
28
+ "designkit": {
29
+ "guideManifest": "./docs/guides/manifest.json"
22
30
  },
23
31
  "files": [
24
- "dist"
32
+ "dist",
33
+ "cli",
34
+ "docs/guides"
25
35
  ],
26
36
  "peerDependencies": {
27
37
  "react": "^19.0.0",
@@ -38,7 +48,7 @@
38
48
  },
39
49
  "devDependencies": {
40
50
  "@eslint/js": "^10.0.1",
41
- "@loykin/gridkit": "^0.2.0-dev.4",
51
+ "@loykin/gridkit": "^0.2.2",
42
52
  "@typescript/native": "npm:typescript@~7.0.2",
43
53
  "@types/node": "^26.0.0",
44
54
  "@types/react": "^19.2.14",
@@ -58,7 +68,8 @@
58
68
  "ui",
59
69
  "components",
60
70
  "tailwindcss",
61
- "design-system"
71
+ "design-system",
72
+ "ai-guides"
62
73
  ],
63
74
  "license": "MIT",
64
75
  "scripts": {
@@ -66,6 +77,7 @@
66
77
  "build:js": "tsup",
67
78
  "build:css": "node scripts/build-css.mjs",
68
79
  "dev": "concurrently \"tsup --watch\" \"pnpm --filter designkit-playground dev\"",
80
+ "guide": "node cli/designkit.mjs guide",
69
81
  "type-check": "tsc --noEmit",
70
82
  "lint": "eslint src --ext .ts,.tsx",
71
83
  "lint:fix": "eslint src --ext .ts,.tsx --fix",