anentrypoint-design 0.0.381 → 0.0.383

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 (54) hide show
  1. package/dist/247420.css +406 -180
  2. package/dist/247420.js +26 -26
  3. package/package.json +1 -1
  4. package/src/components/content/avatar.js +31 -0
  5. package/src/components/content/charts.js +50 -0
  6. package/src/components/content/cli.js +46 -0
  7. package/src/components/content/feedback.js +77 -0
  8. package/src/components/content/fields.js +136 -0
  9. package/src/components/content/hero.js +146 -0
  10. package/src/components/content/lists.js +85 -0
  11. package/src/components/content/panel.js +79 -0
  12. package/src/components/content/row.js +115 -0
  13. package/src/components/content/table.js +97 -0
  14. package/src/components/content/views.js +81 -0
  15. package/src/components/content.js +29 -861
  16. package/src/components/editor-primitives/batch.js +60 -0
  17. package/src/components/editor-primitives/chrome.js +146 -0
  18. package/src/components/editor-primitives/collapse.js +46 -0
  19. package/src/components/editor-primitives/context-menu.js +127 -0
  20. package/src/components/editor-primitives/diagnostics.js +44 -0
  21. package/src/components/editor-primitives/focus-trap.js +26 -0
  22. package/src/components/editor-primitives/json-viewer.js +123 -0
  23. package/src/components/editor-primitives/layout.js +89 -0
  24. package/src/components/editor-primitives/modals.js +89 -0
  25. package/src/components/editor-primitives/pager.js +74 -0
  26. package/src/components/editor-primitives/property-grid.js +57 -0
  27. package/src/components/editor-primitives/shared.js +18 -0
  28. package/src/components/editor-primitives/split-panel.js +101 -0
  29. package/src/components/editor-primitives/toast.js +59 -0
  30. package/src/components/editor-primitives/tree.js +75 -0
  31. package/src/components/editor-primitives.js +35 -1048
  32. package/src/components/files-modals.js +1 -1
  33. package/src/components/overlay-primitives/approval-prompt.js +38 -0
  34. package/src/components/overlay-primitives/auth-modal.js +89 -0
  35. package/src/components/overlay-primitives/command-palette.js +101 -0
  36. package/src/components/overlay-primitives/emoji-picker.js +103 -0
  37. package/src/components/overlay-primitives/floating.js +146 -0
  38. package/src/components/overlay-primitives/full-screen.js +45 -0
  39. package/src/components/overlay-primitives/menus.js +135 -0
  40. package/src/components/overlay-primitives/popover.js +51 -0
  41. package/src/components/overlay-primitives/roving-menu.js +73 -0
  42. package/src/components/overlay-primitives/settings-popover.js +85 -0
  43. package/src/components/overlay-primitives/tooltip.js +55 -0
  44. package/src/components/overlay-primitives.js +33 -855
  45. package/src/css/app-shell/base.css +54 -1
  46. package/src/css/app-shell/chat-polish.css +39 -32
  47. package/src/css/app-shell/data-density.css +25 -21
  48. package/src/css/app-shell/files.css +37 -29
  49. package/src/css/app-shell/kits-appended.css +92 -50
  50. package/src/css/app-shell/responsive.css +73 -35
  51. package/src/css/app-shell/responsive2-workspace.css +16 -4
  52. package/src/css/app-shell/states-interactions.css +21 -4
  53. package/src/css/app-shell/topbar.css +5 -1
  54. package/src/kits/os/freddie-dashboard.css +2 -2
@@ -0,0 +1,79 @@
1
+ // Panel family — the bordered content container (`Panel`, aliased `Card`),
2
+ // the section wrapper (`Section`), the items[]-to-rows mapper
3
+ // (`PanelFromItems`), and the two tabular panel bodies that only ever appear
4
+ // inside one (`Receipt` key/value table, `Changelog` release list).
5
+
6
+ import * as webjsx from '../../../vendor/webjsx/index.js';
7
+ import { RowLink } from './row.js';
8
+ const h = webjsx.createElement;
9
+
10
+ export function Panel({ title, count, right, style = '', class: className = '', children, kind, id }) {
11
+ const cls = 'panel' + (kind ? ' panel-' + kind : '') + (className ? ' ' + className : '');
12
+ return h('div', { class: cls, style, id: id || null },
13
+ title != null ? h('div', { class: 'panel-head' },
14
+ h('span', {}, title),
15
+ right != null ? right : (count != null ? h('span', {}, String(count)) : null)
16
+ ) : null,
17
+ h('div', { class: 'panel-body' }, ...(Array.isArray(children) ? children : [children]))
18
+ );
19
+ }
20
+
21
+ // Card — semantic alias of Panel; behaves identically.
22
+ export const Card = Panel;
23
+
24
+ // PanelFromItems — the shared 'items[] -> RowLink wrapped in Panel' mapper
25
+ // every portfolio consumer theme.mjs (zellous/wireweave/thebird/247420) had
26
+ // hand-rolled identically: items.map((it,i) => RowLink({code, title, sub,
27
+ // meta, href})) inside a titled Panel. `keyPrefix` seeds each row's stable
28
+ // key (`${keyPrefix}${i}`), matching the consumer convention of a
29
+ // one-letter-per-section prefix (e.g. 'f' for features, 'm' for modules).
30
+ // Field aliasing mirrors the union of shapes actually hand-rolled downstream:
31
+ // title reads `title` then `name`; sub reads `sub` then `desc`; code falls
32
+ // back to a zero-padded 1-based index when the item carries none. `heading`/
33
+ // `count`/`style`/`kind` pass through to Panel unchanged.
34
+ export function PanelFromItems({ heading, items = [], keyPrefix = 'i', count, style, kind, emptyText } = {}) {
35
+ if (!items || !items.length) return emptyText != null ? h('div', { class: 'empty' }, emptyText) : null;
36
+ const rows = items.map((it, i) => {
37
+ const codeVal = it.code != null ? it.code : (it.rank != null ? it.rank : String(i + 1).padStart(2, '0'));
38
+ return RowLink({
39
+ key: keyPrefix + i,
40
+ code: codeVal,
41
+ title: it.title != null ? it.title : it.name,
42
+ sub: it.sub != null ? it.sub : (it.desc != null ? it.desc : ''),
43
+ meta: it.meta != null ? it.meta : '',
44
+ href: it.href || '#'
45
+ });
46
+ });
47
+ return Panel({ title: heading, count, style, kind, children: rows });
48
+ }
49
+
50
+ export function Section({ title, eyebrow, children, id }) {
51
+ return h('section', { class: 'ds-section', id: id || null },
52
+ eyebrow ? h('span', { class: 'eyebrow' }, eyebrow) : null,
53
+ title ? h('h3', {}, title) : null,
54
+ ...(Array.isArray(children) ? children : [children])
55
+ );
56
+ }
57
+
58
+ export function Receipt({ rows = [], emptyText = 'nothing here yet' }) {
59
+ if (!rows.length) return h('div', { class: 'empty' }, emptyText);
60
+ return h('table', { class: 'kv' },
61
+ h('tbody', {}, ...rows.map(([k, v], i) =>
62
+ h('tr', { key: i }, h('td', {}, k), h('td', {}, v))
63
+ ))
64
+ );
65
+ }
66
+
67
+ export function Changelog({ entries = [], emptyText = 'no changelog entries yet' }) {
68
+ if (!entries.length) return h('div', { class: 'empty' }, emptyText);
69
+ return Panel({
70
+ kind: 'wide',
71
+ children: entries.map((e, i) =>
72
+ h('div', { key: i, class: 'row ds-changelog-row' },
73
+ h('span', { class: 'code' }, e.date),
74
+ h('span', { class: 'ds-changelog-ver' }, e.ver),
75
+ h('span', { class: 'title' }, e.msg)
76
+ )
77
+ )
78
+ });
79
+ }
@@ -0,0 +1,115 @@
1
+ // Row primitives — the single parameterized list row (`Row`) plus its
2
+ // link-flavoured wrapper (`RowLink`), and the search-term highlighter both
3
+ // use. Row covers every list-row shape in the kit: static, clickable
4
+ // (button semantics + keyboard activation), link, grid-columned, railed,
5
+ // expandable-with-actions, and search-highlighted.
6
+
7
+ import * as webjsx from '../../../vendor/webjsx/index.js';
8
+ const h = webjsx.createElement;
9
+
10
+ // Split a title string around case-insensitive matches of `highlight`, wrapping
11
+ // hits in <mark class="ds-hl">. Every segment is a keyed span so the children
12
+ // array never mixes keyed VElements with bare strings (webjsx applyDiff crashes
13
+ // on mixed keying).
14
+ function highlightTitle(title, highlight) {
15
+ const text = String(title);
16
+ const needle = String(highlight).toLowerCase();
17
+ if (!needle) return text;
18
+ const lower = text.toLowerCase();
19
+ const segs = [];
20
+ let pos = 0, n = 0;
21
+ while (pos <= text.length) {
22
+ const hit = lower.indexOf(needle, pos);
23
+ if (hit === -1) break;
24
+ if (hit > pos) segs.push(h('span', { key: 'hs' + n++ }, text.slice(pos, hit)));
25
+ segs.push(h('mark', { key: 'hs' + n++, class: 'ds-hl' }, text.slice(hit, hit + needle.length)));
26
+ pos = hit + needle.length;
27
+ }
28
+ if (!segs.length) return text;
29
+ if (pos < text.length) segs.push(h('span', { key: 'hs' + n++ }, text.slice(pos)));
30
+ return segs;
31
+ }
32
+
33
+ export function Row({ code, rank, title, sub, meta, active, state = 'default', onClick, key, style, href, kind, cols, leading, trailing, target, selected, rail, expanded, highlight, actions, detail }) {
34
+ // `rank` is an alias for `code` (the leading monospace index); callers use
35
+ // either name. `rail` renders a thin colour bar at the row's leading edge as
36
+ // a status indicator (tone: green | purple | flame | <any token>).
37
+ const codeVal = code != null ? code : rank;
38
+ // Support legacy active/selected props for backward compatibility
39
+ const isActive = state === 'active' || (state === 'default' && (active || selected));
40
+ const isLink = kind === 'link' || (href != null && !onClick);
41
+ const isButton = !isLink && !!onClick;
42
+ const stateCls = state === 'disabled' ? ' row-state-disabled' : (state === 'error' ? ' row-state-error' : '');
43
+ // With no leading/code, the title would otherwise land in the narrow code
44
+ // column and wrap; `row-nocode` collapses that column so the title gets the
45
+ // full width (meta still pinned right).
46
+ const noLead = codeVal == null && leading == null;
47
+ const cls = 'row' + (isActive ? ' active' : '') + stateCls + (cols ? ' row-grid' : '') + (noLead && !cols ? ' row-nocode' : '') + (rail ? ' rail-' + rail : '');
48
+ const isDisabled = state === 'disabled';
49
+ const props = { key, class: cls, style: cols ? `${style ? style + ';' : ''}grid-template-columns:${cols}` : style };
50
+ if (isLink) {
51
+ props.href = href || '#';
52
+ if (target) props.target = target;
53
+ } else if (isButton && !isDisabled) {
54
+ // Clickable div needs button semantics + keyboard activation for a11y parity.
55
+ // A disabled row is inert: no click, no button role, no tab stop.
56
+ props.onclick = onClick;
57
+ props.role = 'button';
58
+ props.tabindex = '0';
59
+ props.onkeydown = (e) => {
60
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(e); }
61
+ };
62
+ // When the row is a disclosure toggle (host passes a boolean `expanded`),
63
+ // announce its open/closed state so AT users hear "expanded/collapsed".
64
+ // Omitted entirely for plain action buttons (expanded === undefined).
65
+ if (expanded === true || expanded === false) props['aria-expanded'] = expanded ? 'true' : 'false';
66
+ }
67
+ if (isDisabled) props['aria-disabled'] = 'true';
68
+ if (isActive && (isLink || isButton)) props['aria-current'] = isActive ? 'page' : null;
69
+ // `highlight` wraps case-insensitive matches in the title in <mark class="ds-hl">.
70
+ // The segments live inside a single wrapper span so the title's child list
71
+ // never mixes keyed and unkeyed siblings.
72
+ const titleNode = (highlight && typeof title === 'string')
73
+ ? h('span', {}, ...[].concat(highlightTitle(title, highlight)))
74
+ : title;
75
+ // `actions` render ONLY when the row is expanded, as a sibling action strip
76
+ // inside the row container; each button stops propagation so it never fires
77
+ // the row onClick.
78
+ const actionRow = (expanded === true && Array.isArray(actions) && actions.length)
79
+ ? h('span', { class: 'row-actions', role: 'group', 'aria-label': 'row actions' },
80
+ ...actions.map((a, i) => h('button', {
81
+ key: 'ract' + i,
82
+ type: 'button',
83
+ class: 'row-act',
84
+ title: a.title || a.label,
85
+ 'aria-label': a.title || a.label,
86
+ onclick: (e) => { e.stopPropagation(); a.onClick && a.onClick(e); },
87
+ onkeydown: (e) => { e.stopPropagation(); },
88
+ }, a.label)))
89
+ : null;
90
+ // Color is not the only status channel: emit a visually-hidden word for the
91
+ // meaningful rail tones (error/subagent) so AT and color-blind users get the
92
+ // state. green is the unremarkable default - announcing "ok" everywhere would
93
+ // be AT noise - so it emits nothing.
94
+ const railWord = rail === 'flame' ? 'error' : rail === 'purple' ? 'subagent' : null;
95
+ // `detail` renders as a sibling block AFTER the title/meta children (its own
96
+ // line via flex-basis:100% in .ds-row-detail), not inside the title span.
97
+ // The same `highlight` search term that marks matches in the collapsed
98
+ // title previously stopped applying the moment a row expanded - the
99
+ // expanded body (often the ONLY place a match beyond the 220-char title
100
+ // window is actually visible) rendered as plain unmarked text.
101
+ const detailNode = (highlight && typeof detail === 'string')
102
+ ? h('span', {}, ...[].concat(highlightTitle(detail, highlight)))
103
+ : detail;
104
+ return h(isLink ? 'a' : 'div', props,
105
+ railWord ? h('span', { class: 'sr-only' }, railWord) : null,
106
+ leading != null ? leading : (codeVal != null ? h('span', { class: 'code' }, codeVal) : null),
107
+ h('span', { class: 'title', title: typeof title === 'string' ? title : undefined }, titleNode, sub ? h('span', { class: 'sub', title: typeof sub === 'string' ? sub : undefined }, sub) : null),
108
+ trailing != null ? trailing : (meta != null ? h('span', { class: 'meta' }, meta) : null),
109
+ actionRow,
110
+ detail != null ? h('pre', { class: 'ds-row-detail' }, detailNode) : null);
111
+ }
112
+
113
+ export function RowLink({ code, title, sub, meta, href = '#', key, target }) {
114
+ return Row({ code, title, sub, meta, href, kind: 'link', key, target });
115
+ }
@@ -0,0 +1,97 @@
1
+ // Tables — the generic `Table` primitive plus the two domain tables built
2
+ // directly on it: HealthTable (a health-check map with per-value tone
3
+ // inference) and ProcessRegistryTable (a live long-lived-process census).
4
+
5
+ import * as webjsx from '../../../vendor/webjsx/index.js';
6
+ import { Icon, Chip } from '../shell.js';
7
+ const h = webjsx.createElement;
8
+
9
+ export function Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet', rowLabels, striped = false, compact = false, sortable = false, sortKey, sortDir = 'asc', onSort }) {
10
+ if (!rows || rows.length === 0) return h('div', { class: 'empty' }, emptyText);
11
+ // rowLabels lets callers supply a plain-text label per row when the first
12
+ // cell is a vnode (so the aria-label is meaningful, not the literal 'row').
13
+ const labelFor = (row, i) => {
14
+ if (Array.isArray(rowLabels) && rowLabels[i] != null) return String(rowLabels[i]);
15
+ const c = row[0];
16
+ return c == null ? 'row' : (typeof c === 'object' ? 'row' : String(c));
17
+ };
18
+ // Native <table>/<tr>/<th>/<td> already carry the correct implicit ARIA
19
+ // roles — explicit role="table"/row/columnheader/cell is redundant and only
20
+ // risks overriding native semantics, so it is omitted.
21
+ // Scroll containment lives on the component itself: a wide table used
22
+ // outside a Panel must never force page-level horizontal scroll.
23
+ // striped/compact are opt-in density modifiers (webgeist g-table parity) —
24
+ // default false so the existing contract/visual is byte-unchanged.
25
+ const wrapClass = 'ds-table-wrap' + (striped ? ' is-striped' : '') + (compact ? ' is-compact' : '');
26
+ // sortable is opt-in (default false, byte-unchanged for existing callers):
27
+ // a header becomes a real <button> announcing aria-sort, dispatching
28
+ // onSort(headerIndex) so the HOST owns the actual row-ordering logic (this
29
+ // component has no opinion on comparator/locale/type - it only renders the
30
+ // control and current state). A docstudio-style dense admin table needs
31
+ // sortable columns; Table previously had no way to express that at all.
32
+ const thFor = (hd, i) => {
33
+ if (!sortable || !onSort) return h('th', { key: i, scope: 'col' }, hd);
34
+ const isActive = sortKey === i;
35
+ const ariaSort = isActive ? (sortDir === 'desc' ? 'descending' : 'ascending') : 'none';
36
+ return h('th', { key: i, scope: 'col', 'aria-sort': ariaSort },
37
+ h('button', { type: 'button', class: 'ds-table-sort-btn' + (isActive ? ' is-active' : ''), onclick: () => onSort(i) },
38
+ h('span', { class: 'ds-table-sort-label' }, hd),
39
+ isActive ? Icon(sortDir === 'desc' ? 'chevron-down' : 'chevron-up', { size: 12 }) : null));
40
+ };
41
+ return h('div', { class: wrapClass }, h('table', {},
42
+ h('thead', {}, h('tr', {}, ...headers.map((hd, i) => thFor(hd, i)))),
43
+ h('tbody', {}, ...rows.map((row, i) => h('tr', {
44
+ key: i,
45
+ class: onRowClick ? 'clickable' : '',
46
+ onclick: onRowClick ? () => onRowClick(i) : null,
47
+ // Space scrolls by default — preventDefault on Space (and Enter) so
48
+ // keyboard activation matches click without page jump.
49
+ ...(onRowClick ? { tabindex: '0', role: 'button', 'aria-label': 'open ' + labelFor(row, i), onkeydown: (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); onRowClick(i); } } } : {})
50
+ }, ...row.map((c, j) => h('td', { key: j }, c == null ? '' : (typeof c === 'object' ? c : String(c)))))))));
51
+ }
52
+
53
+ // HealthTable — generic health-check table: given an arbitrary
54
+ // `{checkName: value}` object, infers a Chip tone per row from the value's
55
+ // own type (boolean true/false -> ok/miss Chip, object -> truncated JSON
56
+ // string, else the raw value stringified) so a new backend health check
57
+ // appears with zero per-check hardcoding at the call site. Ported from
58
+ // freddie's health page (src/components/freddie.js's `health` page), which
59
+ // hand-rolled this exact inference inline; promoted here as a reusable
60
+ // primitive for any dashboard/monitoring surface with a health-check map
61
+ // (agentgui's Live tab included). `okLabel`/`missLabel` let a caller
62
+ // localize the two Chip strings; `jsonTruncate` caps the JSON.stringify
63
+ // length for object-shaped values (matches freddie's own truncation width).
64
+ export function HealthTable({ checks = {}, emptyText = 'no health data', okLabel = 'ok', missLabel = 'no', jsonTruncate = 60 } = {}) {
65
+ const entries = Object.entries(checks);
66
+ if (!entries.length) return h('div', { class: 'empty' }, emptyText);
67
+ const rows = entries.map(([name, v]) => {
68
+ let cell;
69
+ if (typeof v === 'object' && v !== null) {
70
+ const s = JSON.stringify(v);
71
+ cell = h('span', { title: s.length > jsonTruncate ? s : null }, s.length > jsonTruncate ? s.slice(0, jsonTruncate) + '…' : s);
72
+ } else if (v === true) cell = Chip({ tone: 'ok', children: okLabel });
73
+ else if (v === false) cell = Chip({ tone: 'miss', children: missLabel });
74
+ else cell = String(v);
75
+ return [name, cell];
76
+ });
77
+ return Table({ headers: ['check', 'status'], rows });
78
+ }
79
+
80
+ // ProcessRegistryTable — generic long-lived-process registry table: given a
81
+ // list of `{kind, key, state}`-shaped rows (any in-flight process the host
82
+ // tracks — an xstate machine, an ACP/direct-runner session, a background
83
+ // job), renders a uniform table. Ported from freddie's `machines` page
84
+ // (src/components/freddie.js), which used this exact shape for its xstate
85
+ // machine census; generalized here since the shape (kind/key/state) applies
86
+ // equally to agentgui's Live tab listing in-flight agent runner processes.
87
+ // `extraColumns` lets a caller append columns beyond the base three (e.g. a
88
+ // stop-action button) without forking the whole table.
89
+ export function ProcessRegistryTable({ processes = [], emptyText = 'no live processes', extraColumns = [] } = {}) {
90
+ if (!processes.length) return h('div', { class: 'empty' }, emptyText);
91
+ const headers = ['kind', 'key', 'state', ...extraColumns.map(c => c.header)];
92
+ const rows = processes.map(p => [
93
+ p.kind || '—', p.key || '—', p.state || '—',
94
+ ...extraColumns.map(c => c.render(p))
95
+ ]);
96
+ return Table({ headers, rows });
97
+ }
@@ -0,0 +1,81 @@
1
+ // Composed page views — the two whole-page compositions assembled from the
2
+ // primitives in this group: HomeView (hero + shipping/works/writing/manifesto
3
+ // sections) and ProjectView (project prose + install + receipt + changelog).
4
+ // Both return a flat array of blocks for a host to render directly.
5
+
6
+ import * as webjsx from '../../../vendor/webjsx/index.js';
7
+ import { Heading, Lede, Dot } from '../shell.js';
8
+ import { Row } from './row.js';
9
+ import { Panel, Section, Receipt, Changelog } from './panel.js';
10
+ import { Hero, Manifesto } from './hero.js';
11
+ import { Install } from './cli.js';
12
+ import { WorksList, WritingList } from './lists.js';
13
+ const h = webjsx.createElement;
14
+
15
+ export function HomeView({ state = {}, onNav, onToggleWork, works = [], posts = [], manifesto = [], currentlyShipping } = {}) {
16
+ return [
17
+ // The page's one deliberate kicker. 'an entrypoint' is the collective's
18
+ // name-as-tagline — it says something the <h1> does not, and it is the
19
+ // masthead position where a print eyebrow actually belongs. The sections
20
+ // below intentionally carry NO eyebrow: each has a heading that already
21
+ // names it, so a kicker there would only restate the <h3> one word
22
+ // shorter. Do not add eyebrows to the sibling sections to "match" this.
23
+ Hero({
24
+ eyebrow: 'an entrypoint',
25
+ title: 'Small, weird, useful tools — built in public.',
26
+ body: '247420 is a creative collective of eight, scattered across three timezones. We have been shipping open-source tools for the web since 2018.',
27
+ accent: 'Some become the future. Most don\'t. That\'s the deal.'
28
+ }),
29
+ // Titleless section: promoted its former eyebrow to the actual heading
30
+ // rather than leaving a kicker hovering over an unnamed panel. The label
31
+ // was carrying the section's only name, so it became the <h3>.
32
+ currentlyShipping ? Section({
33
+ title: 'currently shipping',
34
+ children: Panel({
35
+ kind: 'wide',
36
+ children: currentlyShipping.map((row, i) => {
37
+ const dotNode = Dot({ tone: row.live ? 'live' : 'idle' });
38
+ dotNode.props = { ...dotNode.props, 'aria-label': row.live ? 'live status' : 'idle status' };
39
+ return Row({
40
+ key: i,
41
+ code: dotNode,
42
+ title: row.title, sub: row.sub, meta: row.meta
43
+ });
44
+ })
45
+ })
46
+ }) : null,
47
+ works.length ? Section({
48
+ title: 'Everything else.',
49
+ children: WorksList({ works, openedIndex: state.opened ?? -1, onToggle: onToggleWork })
50
+ }) : null,
51
+ posts.length ? Section({
52
+ title: 'When we have something to say.',
53
+ children: WritingList({ posts })
54
+ }) : null,
55
+ manifesto.length ? Section({
56
+ title: 'Eight people, three timezones, one ongoing conversation.',
57
+ children: Manifesto({ paragraphs: manifesto })
58
+ }) : null
59
+ ].filter(Boolean);
60
+ }
61
+
62
+ export function ProjectView({ project = {}, copied, onCopy } = {}) {
63
+ return [
64
+ h('div', { class: 'ds-prose' },
65
+ Heading({ level: 1, children: project.name }),
66
+ Lede({ children: project.tagline })
67
+ ),
68
+ project.install ? [
69
+ Heading({ level: 3, children: 'install' }),
70
+ Install({ cmd: project.install, copied, onCopy }),
71
+ ] : null,
72
+ project.receipt ? [
73
+ Heading({ level: 3, children: 'by the numbers' }),
74
+ Receipt({ rows: project.receipt }),
75
+ ] : null,
76
+ project.changelog ? [
77
+ Heading({ level: 3, children: 'recent releases' }),
78
+ Changelog({ entries: project.changelog })
79
+ ] : null
80
+ ].filter(Boolean).flat();
81
+ }