@c9up/station 0.1.6 → 0.1.7

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.
Files changed (46) hide show
  1. package/dist/StationProvider.d.ts +94 -18
  2. package/dist/StationProvider.d.ts.map +1 -1
  3. package/dist/StationProvider.js +693 -148
  4. package/dist/StationProvider.js.map +1 -1
  5. package/dist/views/form.d.ts +56 -16
  6. package/dist/views/form.d.ts.map +1 -1
  7. package/dist/views/form.js +53 -75
  8. package/dist/views/form.js.map +1 -1
  9. package/dist/views/list.d.ts +43 -5
  10. package/dist/views/list.d.ts.map +1 -1
  11. package/dist/views/list.js +63 -60
  12. package/dist/views/list.js.map +1 -1
  13. package/dist/views/login.d.ts +20 -8
  14. package/dist/views/login.d.ts.map +1 -1
  15. package/dist/views/login.js +15 -33
  16. package/dist/views/login.js.map +1 -1
  17. package/dist/views/show.d.ts +18 -4
  18. package/dist/views/show.d.ts.map +1 -1
  19. package/dist/views/show.js +29 -15
  20. package/dist/views/show.js.map +1 -1
  21. package/package.json +21 -4
  22. package/src/StationProvider.ts +941 -155
  23. package/src/views/form.ts +111 -113
  24. package/src/views/list.ts +108 -75
  25. package/src/views/login.ts +31 -44
  26. package/src/views/show.ts +47 -18
  27. package/templates/errors/404.inker +4 -0
  28. package/templates/form.inker +3 -0
  29. package/{dist/views/layout.js → templates/layout.inker} +10 -35
  30. package/templates/list.inker +8 -0
  31. package/templates/login.inker +4 -0
  32. package/templates/show.inker +4 -0
  33. package/dist/views/errors/404.d.ts +0 -12
  34. package/dist/views/errors/404.d.ts.map +0 -1
  35. package/dist/views/errors/404.js +0 -19
  36. package/dist/views/errors/404.js.map +0 -1
  37. package/dist/views/escape.d.ts +0 -30
  38. package/dist/views/escape.d.ts.map +0 -1
  39. package/dist/views/escape.js +0 -34
  40. package/dist/views/escape.js.map +0 -1
  41. package/dist/views/layout.d.ts +0 -24
  42. package/dist/views/layout.d.ts.map +0 -1
  43. package/dist/views/layout.js.map +0 -1
  44. package/src/views/errors/404.ts +0 -27
  45. package/src/views/escape.ts +0 -46
  46. package/src/views/layout.ts +0 -90
@@ -1,85 +1,88 @@
1
1
  /**
2
2
  * `GET /admin/<slug>` — paginated list view.
3
3
  *
4
- * Pure `(input) => string` renderer. Every dynamic value flows through
5
- * `escapeHtml()` or `encodeURIComponent()` (URL components). The
6
- * "interpolation must wrap" rule is grep-enforced by
7
- * `tests/unit/no-unescaped-interpolation.test.ts`.
4
+ * Pure `(input) => ListViewModel` builder. It precomputes everything the
5
+ * inker `{{ }}` grammar cannot express — `encodeURIComponent` on URL
6
+ * components (inker only HTML-escapes), the pager-collapse shape, the
7
+ * caption bounds, and each row flattened to pre-stringified `cells` — and
8
+ * hands it to `templates/list.inker`, which iterates and interpolates.
9
+ * inker owns the HTML-escaping (the retired `escape.ts` no longer runs here).
8
10
  */
9
- import { escapeHtml, safeHtml } from "./escape.js";
10
- import { renderLayout } from "./layout.js";
11
- export function renderListPage(input) {
11
+ export function buildListViewModel(input) {
12
12
  const { resource, rows, columns, pkColumn, page, perPage, total, lastPage } = input;
13
13
  const slug = encodeURIComponent(resource.name);
14
- const body = rows.length === 0
15
- ? renderEmptyState(resource)
16
- : renderTable(rows, columns, pkColumn, slug);
17
- const pager = renderPager(slug, page, perPage, lastPage);
18
- const caption = renderCaption(page, perPage, total);
19
- return renderLayout({
14
+ const columnKeys = columns.map((c) => c.propertyKey);
15
+ return {
20
16
  title: resource.label,
21
- bodyHtml: safeHtml(`<h1>${escapeHtml(resource.label)}</h1>${body}${pager}${caption}`),
22
- });
23
- }
24
- function renderEmptyState(resource) {
25
- return `<p class="st-empty">No ${escapeHtml(resource.label.toLowerCase())} yet.</p>`;
17
+ heading: resource.label,
18
+ labelLower: resource.label.toLowerCase(),
19
+ empty: rows.length === 0,
20
+ columns: columnKeys,
21
+ rows: rows.map((row) => ({
22
+ cells: columns.map((c) => stringifyCell(row[c.propertyKey])),
23
+ showHref: `/admin/${slug}/${encodeURIComponent(String(row[pkColumn] ?? ""))}`,
24
+ })),
25
+ pager: buildPager(slug, page, perPage, lastPage),
26
+ caption: buildCaption(page, perPage, total),
27
+ };
26
28
  }
27
- function renderTable(rows, columns, pkColumn, slug) {
28
- const headers = columns
29
- .map((c) => `<th>${escapeHtml(c.propertyKey)}</th>`)
30
- .join("");
31
- const headerRow = `<thead><tr>${headers}<th></th></tr></thead>`;
32
- const bodyRows = rows
33
- .map((row) => {
34
- const cells = columns
35
- .map((c) => `<td>${escapeHtml(row[c.propertyKey])}</td>`)
36
- .join("");
37
- const id = String(row[pkColumn] ?? "");
38
- const showLink = `<td><a href="/admin/${slug}/${encodeURIComponent(id)}">Show</a></td>`;
39
- return `<tr>${cells}${showLink}</tr>`;
40
- })
41
- .join("");
42
- return `<table>${headerRow}<tbody>${bodyRows}</tbody></table>`;
29
+ /** Match the retired escaper's `null | undefined` → `""` behaviour. */
30
+ function stringifyCell(value) {
31
+ return value === null || value === undefined ? "" : String(value);
43
32
  }
44
- function renderPager(slug, page, perPage, lastPage) {
33
+ function buildPager(slug, page, perPage, lastPage) {
45
34
  const pp = encodeURIComponent(String(perPage));
46
- const prev = page > 1
47
- ? `<a href="/admin/${slug}?page=${page - 1}&perPage=${pp}">« Prev</a>`
48
- : `<span class="st-disabled">« Prev</span>`;
49
- const next = page < lastPage
50
- ? `<a href="/admin/${slug}?page=${page + 1}&perPage=${pp}">Next »</a>`
51
- : `<span class="st-disabled">Next »</span>`;
52
- const numbers = renderPageNumbers(slug, page, lastPage, pp);
53
- return `<div class="st-pager">${prev}${numbers}${next}</div>`;
35
+ const href = (n) => `/admin/${slug}?page=${n}&perPage=${pp}`;
36
+ return {
37
+ prev: { href: page > 1 ? href(page - 1) : "", disabled: page <= 1 },
38
+ next: {
39
+ href: page < lastPage ? href(page + 1) : "",
40
+ disabled: page >= lastPage,
41
+ },
42
+ pages: buildPageNumbers(slug, page, lastPage, pp),
43
+ };
54
44
  }
55
- function renderPageNumbers(slug, page, lastPage, pp) {
56
- const link = (n) => n === page
57
- ? `<strong>${n}</strong>`
58
- : `<a href="/admin/${slug}?page=${n}&perPage=${pp}">${n}</a>`;
45
+ function buildPageNumbers(slug, page, lastPage, pp) {
46
+ const href = (n) => `/admin/${slug}?page=${n}&perPage=${pp}`;
47
+ const numbered = (n) => ({
48
+ n,
49
+ href: href(n),
50
+ isCurrent: n === page,
51
+ isEllipsis: false,
52
+ });
53
+ const ellipsis = {
54
+ n: 0,
55
+ href: "",
56
+ isCurrent: false,
57
+ isEllipsis: true,
58
+ };
59
59
  if (lastPage <= 7) {
60
60
  const parts = [];
61
61
  for (let i = 1; i <= lastPage; i++)
62
- parts.push(link(i));
63
- return parts.join("");
62
+ parts.push(numbered(i));
63
+ return parts;
64
64
  }
65
65
  // Collapsed shape: 1 … current-1, current, current+1 … lastPage
66
- const parts = [link(1)];
66
+ const parts = [numbered(1)];
67
67
  const start = Math.max(2, page - 1);
68
68
  const end = Math.min(lastPage - 1, page + 1);
69
69
  if (start > 2)
70
- parts.push(`<span class="st-ellipsis">…</span>`);
70
+ parts.push(ellipsis);
71
71
  for (let i = start; i <= end; i++)
72
- parts.push(link(i));
72
+ parts.push(numbered(i));
73
73
  if (end < lastPage - 1)
74
- parts.push(`<span class="st-ellipsis">…</span>`);
75
- parts.push(link(lastPage));
76
- return parts.join("");
74
+ parts.push(ellipsis);
75
+ parts.push(numbered(lastPage));
76
+ return parts;
77
77
  }
78
- function renderCaption(page, perPage, total) {
78
+ function buildCaption(page, perPage, total) {
79
79
  if (total === 0)
80
- return "";
81
- const start = (page - 1) * perPage + 1;
82
- const end = Math.min(page * perPage, total);
83
- return `<p class="st-caption">Showing ${start}–${end} of ${total}</p>`;
80
+ return { show: false, start: 0, end: 0, total };
81
+ return {
82
+ show: true,
83
+ start: (page - 1) * perPage + 1,
84
+ end: Math.min(page * perPage, total),
85
+ total,
86
+ };
84
87
  }
85
88
  //# sourceMappingURL=list.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/views/list.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/views/list.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,12 @@
5
5
  * `buildLoginHandler` runs them through `auth.authenticate(...)` and
6
6
  * sets a session cookie on success.
7
7
  *
8
- * Every dynamic value flows through `escapeHtml()` so a tampered
9
- * `?error=…` query parameter cannot smuggle markup into the page.
8
+ * Pure `(input) => LoginViewModel` builder (Story 57.3): every value reaches
9
+ * HTML through `templates/login.inker`'s `{{ }}` auto-escape (the retired
10
+ * `escape.ts` no longer runs here), so a tampered `?error=…` query parameter
11
+ * cannot smuggle markup into the page. The CSRF hidden input is emitted by
12
+ * inker's canonical `{{ csrfField() }}` helper, guarded by the precomputed
13
+ * `csrfEnabled` boolean.
10
14
  */
11
15
  export interface LoginPageInput {
12
16
  /** Pre-fill the email field (e.g. after a failed attempt). */
@@ -15,11 +19,19 @@ export interface LoginPageInput {
15
19
  error?: string;
16
20
  /** Form action path. Default `/admin/login`. */
17
21
  action?: string;
18
- /** Caller-controlled hidden inputs (typically `_csrf`). */
19
- hiddenInputs?: ReadonlyArray<{
20
- name: string;
21
- value: string;
22
- }>;
22
+ /**
23
+ * Whether the host has a CSRF token in the per-request store. When true the
24
+ * template emits `{{ csrfField() }}`; the handler derives it from
25
+ * `ctx.store` (57.3). Replaces the old `hiddenInputs?` array.
26
+ */
27
+ csrfEnabled?: boolean;
23
28
  }
24
- export declare function renderLoginPage(input: LoginPageInput): string;
29
+ export type LoginViewModel = {
30
+ title: string;
31
+ email: string;
32
+ action: string;
33
+ error: string;
34
+ csrfEnabled: boolean;
35
+ };
36
+ export declare function buildLoginViewModel(input: LoginPageInput): LoginViewModel;
25
37
  //# sourceMappingURL=login.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAuC7D"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAUzE"}
@@ -5,40 +5,22 @@
5
5
  * `buildLoginHandler` runs them through `auth.authenticate(...)` and
6
6
  * sets a session cookie on success.
7
7
  *
8
- * Every dynamic value flows through `escapeHtml()` so a tampered
9
- * `?error=…` query parameter cannot smuggle markup into the page.
8
+ * Pure `(input) => LoginViewModel` builder (Story 57.3): every value reaches
9
+ * HTML through `templates/login.inker`'s `{{ }}` auto-escape (the retired
10
+ * `escape.ts` no longer runs here), so a tampered `?error=…` query parameter
11
+ * cannot smuggle markup into the page. The CSRF hidden input is emitted by
12
+ * inker's canonical `{{ csrfField() }}` helper, guarded by the precomputed
13
+ * `csrfEnabled` boolean.
10
14
  */
11
- import { escapeHtml, safeHtml } from "./escape.js";
12
- import { renderLayout } from "./layout.js";
13
- export function renderLoginPage(input) {
14
- const email = input.email ?? "";
15
- const error = input.error;
16
- const action = input.action ?? "/admin/login";
17
- const hiddens = (input.hiddenInputs ?? [])
18
- .map((h) => `<input type="hidden" name="${escapeHtml(h.name)}" value="${escapeHtml(h.value)}">`)
19
- .join("");
20
- const errorBlock = error !== undefined
21
- ? `<p class="st-form-error" role="alert">${escapeHtml(error)}</p>`
22
- : "";
23
- const body = `<h1>Sign in</h1>` +
24
- errorBlock +
25
- `<form class="st-form" method="POST" action="${escapeHtml(action)}">` +
26
- hiddens +
27
- `<div class="st-field">` +
28
- `<label for="f-email">Email</label>` +
29
- `<input id="f-email" type="email" name="email" value="${escapeHtml(email)}" required autocomplete="email" autofocus>` +
30
- `</div>` +
31
- `<div class="st-field">` +
32
- `<label for="f-password">Password</label>` +
33
- `<input id="f-password" type="password" name="password" required autocomplete="current-password">` +
34
- `</div>` +
35
- `<div class="st-form-actions">` +
36
- `<button type="submit">Sign in</button>` +
37
- `</div>` +
38
- `</form>`;
39
- return renderLayout({
15
+ export function buildLoginViewModel(input) {
16
+ return {
40
17
  title: "Sign in",
41
- bodyHtml: safeHtml(body),
42
- });
18
+ email: input.email ?? "",
19
+ // No URL-encoding: the login action is a fixed host-configured path.
20
+ action: input.action ?? "/admin/login",
21
+ // Empty string is falsy in inker, so `{% if error %}` gates the block.
22
+ error: input.error ?? "",
23
+ csrfEnabled: input.csrfEnabled === true,
24
+ };
43
25
  }
44
26
  //# sourceMappingURL=login.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAa3C,MAAM,UAAU,eAAe,CAAC,KAAqB;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,cAAc,CAAC;IAE9C,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;SACxC,GAAG,CACH,CAAC,CAAC,EAAE,EAAE,CACL,8BAA8B,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CACpF;SACA,IAAI,CAAC,EAAE,CAAC,CAAC;IAEX,MAAM,UAAU,GACf,KAAK,KAAK,SAAS;QAClB,CAAC,CAAC,yCAAyC,UAAU,CAAC,KAAK,CAAC,MAAM;QAClE,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,IAAI,GACT,kBAAkB;QAClB,UAAU;QACV,+CAA+C,UAAU,CAAC,MAAM,CAAC,IAAI;QACrE,OAAO;QACP,wBAAwB;QACxB,oCAAoC;QACpC,wDAAwD,UAAU,CAAC,KAAK,CAAC,4CAA4C;QACrH,QAAQ;QACR,wBAAwB;QACxB,0CAA0C;QAC1C,kGAAkG;QAClG,QAAQ;QACR,+BAA+B;QAC/B,wCAAwC;QACxC,QAAQ;QACR,SAAS,CAAC;IAEX,OAAO,YAAY,CAAC;QACnB,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC;KACxB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACxD,OAAO;QACN,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;QACxB,qEAAqE;QACrE,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,cAAc;QACtC,uEAAuE;QACvE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW,KAAK,IAAI;KACvC,CAAC;AACH,CAAC"}
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * `GET /admin/<slug>/:id` — detail view (columns only in 54.2).
3
3
  *
4
- * Relations are deferred — see story spec AC17 Spec Deviations. Every
5
- * dynamic value flows through `escapeHtml()`; URL components through
6
- * `encodeURIComponent()`.
4
+ * Pure `(input) => ShowViewModel` builder. Columns × row are flattened into
5
+ * a `fields` array with pre-stringified values (inker's grammar has no
6
+ * dynamic `row[key]` indexing), and the slug is `encodeURIComponent`-ed for
7
+ * the back-link. `templates/show.inker` iterates + interpolates; inker owns
8
+ * the HTML-escaping (the retired `escape.ts` no longer runs here).
7
9
  */
8
10
  import type { ColumnMetadata } from "@c9up/atlas";
9
11
  import type { Resource } from "../types.js";
@@ -13,5 +15,17 @@ export interface ShowPageInput {
13
15
  columns: ReadonlyArray<ColumnMetadata>;
14
16
  pkColumn: string;
15
17
  }
16
- export declare function renderShowPage(input: ShowPageInput): string;
18
+ /** A single `<dt>/<dd>` pair: the column key + its pre-stringified value. */
19
+ export interface ShowField {
20
+ label: string;
21
+ value: string;
22
+ }
23
+ export type ShowViewModel = {
24
+ title: string;
25
+ heading: string;
26
+ fields: ShowField[];
27
+ backHref: string;
28
+ backLabel: string;
29
+ };
30
+ export declare function buildShowViewModel(input: ShowPageInput): ShowViewModel;
17
31
  //# sourceMappingURL=show.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAgB3D"}
1
+ {"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAKD,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAuBtE"}
@@ -1,24 +1,38 @@
1
1
  /**
2
2
  * `GET /admin/<slug>/:id` — detail view (columns only in 54.2).
3
3
  *
4
- * Relations are deferred — see story spec AC17 Spec Deviations. Every
5
- * dynamic value flows through `escapeHtml()`; URL components through
6
- * `encodeURIComponent()`.
4
+ * Pure `(input) => ShowViewModel` builder. Columns × row are flattened into
5
+ * a `fields` array with pre-stringified values (inker's grammar has no
6
+ * dynamic `row[key]` indexing), and the slug is `encodeURIComponent`-ed for
7
+ * the back-link. `templates/show.inker` iterates + interpolates; inker owns
8
+ * the HTML-escaping (the retired `escape.ts` no longer runs here).
7
9
  */
8
- import { escapeHtml, safeHtml } from "./escape.js";
9
- import { renderLayout } from "./layout.js";
10
- export function renderShowPage(input) {
10
+ export function buildShowViewModel(input) {
11
11
  const { resource, row, columns, pkColumn } = input;
12
12
  const id = String(row[pkColumn] ?? "");
13
13
  const slug = encodeURIComponent(resource.name);
14
- const heading = `${escapeHtml(resource.label)} #${escapeHtml(id)}`;
15
- const dl = columns
16
- .map((c) => `<dt>${escapeHtml(c.propertyKey)}</dt><dd>${escapeHtml(row[c.propertyKey])}</dd>`)
17
- .join("");
18
- const backLink = `<a class="st-backlink" href="/admin/${slug}">← Back to ${escapeHtml(resource.label)}</a>`;
19
- return renderLayout({
20
- title: `${resource.label} #${id}`,
21
- bodyHtml: safeHtml(`<h1>${heading}</h1><dl>${dl}</dl>${backLink}`),
22
- });
14
+ // HTML-escaping is a per-character replace, so it distributes over
15
+ // concatenation: the retired view escaped label and id separately, so
16
+ // composing the heading here then letting inker's `{{ }}` escape it whole
17
+ // stays equivalent — inker escapes a superset of the retired escape.ts
18
+ // (backtick, U+2028/U+2029), so the output is byte-identical except for
19
+ // those extra chars, and at minimum equally safe. Built via join (no `+`
20
+ // concat, no interpolation slot) so neither biome's useTemplate nor the
21
+ // no-unescaped-interpolation grep-ban fires on this now-data-only file.
22
+ const heading = [resource.label, id].join(" #");
23
+ return {
24
+ title: heading,
25
+ heading,
26
+ fields: columns.map((c) => ({
27
+ label: c.propertyKey,
28
+ value: stringifyValue(row[c.propertyKey]),
29
+ })),
30
+ backHref: `/admin/${slug}`,
31
+ backLabel: resource.label,
32
+ };
33
+ }
34
+ /** Match the retired escaper's `null | undefined` → empty-output behaviour. */
35
+ function stringifyValue(value) {
36
+ return value === null || value === undefined ? "" : String(value);
23
37
  }
24
38
  //# sourceMappingURL=show.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAS3C,MAAM,UAAU,cAAc,CAAC,KAAoB;IAClD,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;IACnE,MAAM,EAAE,GAAG,OAAO;SAChB,GAAG,CACH,CAAC,CAAC,EAAE,EAAE,CACL,OAAO,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAClF;SACA,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,MAAM,QAAQ,GAAG,uCAAuC,IAAI,eAAe,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;IAC5G,OAAO,YAAY,CAAC;QACnB,KAAK,EAAE,GAAG,QAAQ,CAAC,KAAK,KAAK,EAAE,EAAE;QACjC,QAAQ,EAAE,QAAQ,CAAC,OAAO,OAAO,YAAY,EAAE,QAAQ,QAAQ,EAAE,CAAC;KAClE,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6BH,MAAM,UAAU,kBAAkB,CAAC,KAAoB;IACtD,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/C,mEAAmE;IACnE,sEAAsE;IACtE,0EAA0E;IAC1E,uEAAuE;IACvE,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACN,KAAK,EAAE,OAAO;QACd,OAAO;QACP,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,KAAK,EAAE,CAAC,CAAC,WAAW;YACpB,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;SACzC,CAAC,CAAC;QACH,QAAQ,EAAE,UAAU,IAAI,EAAE;QAC1B,SAAS,EAAE,QAAQ,CAAC,KAAK;KACzB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/station",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Station — admin scaffolding for the Ream framework. Declarative resource catalogue (defineResource) backing list/show/create/edit/destroy routes, audit, policies.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,29 +11,44 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
+ "./provider": {
15
+ "types": "./dist/StationProvider.d.ts",
16
+ "import": "./dist/StationProvider.js"
17
+ },
14
18
  "./services/main": {
15
19
  "types": "./dist/services/main.d.ts",
16
20
  "import": "./dist/services/main.js"
17
21
  }
18
22
  },
19
23
  "peerDependencies": {
20
- "@c9up/atlas": "^0.1.0",
24
+ "@c9up/atlas": "^0.2.0",
25
+ "@c9up/inker": "^0.1.0",
21
26
  "@c9up/ream": "^0.1.0",
27
+ "@c9up/rune": "^0.1.8",
22
28
  "@c9up/warden": "^0.1.0"
23
29
  },
24
30
  "peerDependenciesMeta": {
25
31
  "@c9up/atlas": {
26
32
  "optional": true
27
33
  },
34
+ "@c9up/inker": {
35
+ "optional": true
36
+ },
28
37
  "@c9up/ream": {
29
38
  "optional": true
30
39
  },
40
+ "@c9up/rune": {
41
+ "optional": true
42
+ },
31
43
  "@c9up/warden": {
32
44
  "optional": true
33
45
  }
34
46
  },
35
47
  "devDependencies": {
36
- "@c9up/atlas": "^0.1.0",
48
+ "@biomejs/biome": "^2.4.10",
49
+ "@c9up/atlas": "^0.2.0",
50
+ "@c9up/inker": "^0.1.0",
51
+ "@c9up/rune": "^0.1.8",
37
52
  "@types/node": "^22.19.15",
38
53
  "@vitest/coverage-v8": "^4.1.2",
39
54
  "reflect-metadata": "^0.2.2",
@@ -44,7 +59,8 @@
44
59
  "LICENSE",
45
60
  "README.md",
46
61
  "dist",
47
- "src"
62
+ "src",
63
+ "templates"
48
64
  ],
49
65
  "publishConfig": {
50
66
  "access": "public"
@@ -54,6 +70,7 @@
54
70
  "url": "git+https://github.com/C9up/station.git"
55
71
  },
56
72
  "scripts": {
73
+ "prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
57
74
  "build": "tsc -p tsconfig.build.json",
58
75
  "typecheck": "tsc --noEmit",
59
76
  "test": "vitest run",