@deskwork/studio 0.17.1 → 0.19.0
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.
- package/dist/pages/dashboard/affordances.d.ts +19 -24
- package/dist/pages/dashboard/affordances.d.ts.map +1 -1
- package/dist/pages/dashboard/affordances.js +52 -41
- package/dist/pages/dashboard/affordances.js.map +1 -1
- package/dist/pages/dashboard/affordances.ts +56 -45
- package/dist/pages/dashboard/header.d.ts +9 -3
- package/dist/pages/dashboard/header.d.ts.map +1 -1
- package/dist/pages/dashboard/header.js +10 -30
- package/dist/pages/dashboard/header.js.map +1 -1
- package/dist/pages/dashboard/header.ts +10 -32
- package/dist/pages/dashboard/section.d.ts +42 -15
- package/dist/pages/dashboard/section.d.ts.map +1 -1
- package/dist/pages/dashboard/section.js +118 -49
- package/dist/pages/dashboard/section.js.map +1 -1
- package/dist/pages/dashboard/section.ts +120 -53
- package/dist/pages/dashboard.d.ts +23 -17
- package/dist/pages/dashboard.d.ts.map +1 -1
- package/dist/pages/dashboard.js +105 -25
- package/dist/pages/dashboard.js.map +1 -1
- package/dist/pages/dashboard.ts +107 -26
- package/dist/pages/entry-review/decision-strip.d.ts.map +1 -1
- package/dist/pages/entry-review/decision-strip.js +4 -0
- package/dist/pages/entry-review/decision-strip.js.map +1 -1
- package/dist/pages/entry-review/edit-toolbar.d.ts +1 -1
- package/dist/pages/entry-review/edit-toolbar.d.ts.map +1 -1
- package/dist/pages/entry-review/edit-toolbar.js +9 -1
- package/dist/pages/entry-review/edit-toolbar.js.map +1 -1
- package/dist/pages/entry-review/index.d.ts.map +1 -1
- package/dist/pages/entry-review/index.js +14 -4
- package/dist/pages/entry-review/index.js.map +1 -1
- package/dist/pages/entry-review/mobile-bar.d.ts +62 -0
- package/dist/pages/entry-review/mobile-bar.d.ts.map +1 -0
- package/dist/pages/entry-review/mobile-bar.js +155 -0
- package/dist/pages/entry-review/mobile-bar.js.map +1 -0
- package/dist/pages/entry-review/outline-drawer.d.ts +17 -7
- package/dist/pages/entry-review/outline-drawer.d.ts.map +1 -1
- package/dist/pages/entry-review/outline-drawer.js +58 -14
- package/dist/pages/entry-review/outline-drawer.js.map +1 -1
- package/dist/pages/help.js +1 -1
- package/dist/pages/help.ts +1 -1
- package/dist/pages/index.d.ts +8 -6
- package/dist/pages/index.d.ts.map +1 -1
- package/dist/pages/index.js +16 -14
- package/dist/pages/index.js.map +1 -1
- package/dist/pages/index.ts +16 -14
- package/dist/pages/layout.js +1 -1
- package/dist/pages/layout.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single-stage section renderer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* with a section heading (stage name + entry count) and either
|
|
6
|
-
* of rows or an empty-state placeholder.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Each of the eight stage sections (plus the Distribution placeholder)
|
|
5
|
+
* renders with a section heading (stage name + entry count) and either
|
|
6
|
+
* a list of rows or an empty-state placeholder. Each row carries the
|
|
7
|
+
* entry's slug, title, updated-at timestamp, and stage-gated verb
|
|
8
|
+
* buttons. Per DESKWORK-STATE-MACHINE.md Commandment III, rows do NOT
|
|
9
|
+
* surface iteration counts or reviewState — those were retired in
|
|
10
|
+
* v0.19 along with the legacy reviewState concept.
|
|
11
|
+
*
|
|
12
|
+
* On mobile, each section is fronted by a collapsible tile (see
|
|
13
|
+
* `renderStageTile`); on desktop the tiles are display:none and the
|
|
14
|
+
* `<h2 class="er-section-head">` heading carries the stage name.
|
|
10
15
|
*/
|
|
11
16
|
import { html, unsafe } from "../html.js";
|
|
12
|
-
import {
|
|
17
|
+
import { renderRowActions } from "./affordances.js";
|
|
13
18
|
const STAGE_ORNAMENTS = {
|
|
14
19
|
Ideas: '◇',
|
|
15
20
|
Planned: '§',
|
|
@@ -34,13 +39,16 @@ const STAGE_EMPTY_MESSAGES = {
|
|
|
34
39
|
* Render one entry as a single dashboard row. Carries inline:
|
|
35
40
|
* - slug (linked to the review surface)
|
|
36
41
|
* - title
|
|
37
|
-
* - iteration count for the entry's currentStage
|
|
38
|
-
* - reviewState badge (or an em-dash placeholder)
|
|
39
42
|
* - updatedAt timestamp
|
|
40
43
|
* - per-stage action buttons
|
|
44
|
+
*
|
|
45
|
+
* Per DESKWORK-STATE-MACHINE.md (v5): revisions (the iteration counter)
|
|
46
|
+
* are bookkeeping and do NOT surface in routine UI. The previous
|
|
47
|
+
* "iteration: N" inline display was a violation — operators see
|
|
48
|
+
* revisions only via the View History surface and revert flows.
|
|
49
|
+
* reviewState badges are likewise retired (Commandment III).
|
|
41
50
|
*/
|
|
42
51
|
export function renderRow(entry, index, defaultSite) {
|
|
43
|
-
const iteration = iterationForCurrentStage(entry);
|
|
44
52
|
const reviewLink = `/dev/editorial-review/entry/${entry.uuid}`;
|
|
45
53
|
const search = [entry.slug, entry.title, entry.keywords.join(' ')].join(' ').toLowerCase();
|
|
46
54
|
// Hierarchical entries (slugs containing `/`) get a visual indent
|
|
@@ -58,69 +66,130 @@ export function renderRow(entry, index, defaultSite) {
|
|
|
58
66
|
<span class="er-row-slug"><a href="${reviewLink}"
|
|
59
67
|
title="open the review surface">${entry.slug}</a></span>
|
|
60
68
|
<span class="er-calendar-title">${entry.title}</span>
|
|
61
|
-
<span class="er-calendar-meta er-calendar-meta-iteration"
|
|
62
|
-
data-iteration="${iteration}">iteration: ${iteration}</span>
|
|
63
69
|
<time class="er-calendar-meta er-calendar-meta-updated" data-format="date"
|
|
64
70
|
datetime="${entry.updatedAt}" title="${entry.updatedAt}">${formatDate(entry.updatedAt)}</time>
|
|
65
71
|
</div>
|
|
66
|
-
<span class="er-calendar-status"
|
|
67
|
-
title="open the review surface for ${entry.slug}"
|
|
68
|
-
class="er-stamp-link">${renderReviewStateBadge(entry.reviewState)}</a></span>
|
|
72
|
+
<span class="er-calendar-status" aria-hidden="true"></span>
|
|
69
73
|
${renderRowActions(entry, defaultSite)}
|
|
70
74
|
</div>
|
|
71
75
|
</div>`);
|
|
72
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Render the stage tile (mobile-only collapsible head). Hidden on desktop
|
|
79
|
+
* via dashboard-mobile.css; the existing `<h2 class="er-section-head">`
|
|
80
|
+
* carries the head on desktop and is hidden at <=600px so the tile takes
|
|
81
|
+
* over.
|
|
82
|
+
*
|
|
83
|
+
* Empty stages render the same tile shape but with `is-empty` styling and
|
|
84
|
+
* `disabled` so taps are no-ops (operator can still SEE the empty stage
|
|
85
|
+
* in the pipeline shape — they just can't drill in to nothing).
|
|
86
|
+
*
|
|
87
|
+
* Review-state sub-counts (e.g. "5 · 3 in review") were removed in v0.19
|
|
88
|
+
* per operator: review state isn't user-facing data and is slated for
|
|
89
|
+
* backend removal; the tile shows total entry count only.
|
|
90
|
+
*/
|
|
91
|
+
function renderStageTile(stage, count) {
|
|
92
|
+
const isEmpty = count === 0;
|
|
93
|
+
const classes = isEmpty ? 'er-stage-tile is-empty' : 'er-stage-tile';
|
|
94
|
+
const disabledAttr = isEmpty ? ' disabled' : '';
|
|
95
|
+
return unsafe(html `
|
|
96
|
+
<button class="${classes}" type="button"
|
|
97
|
+
data-stage-tile="${stage}"
|
|
98
|
+
aria-expanded="false"
|
|
99
|
+
aria-controls="stage-${stage.toLowerCase()}"${unsafe(disabledAttr)}>
|
|
100
|
+
<span class="er-stage-tile-glyph" aria-hidden="true">${STAGE_ORNAMENTS[stage]}</span>
|
|
101
|
+
<span class="er-stage-tile-name">${stage}</span>
|
|
102
|
+
<span class="er-stage-tile-count"><span class="num">${count}</span></span>
|
|
103
|
+
<span class="er-stage-tile-chev" aria-hidden="true">›</span>
|
|
104
|
+
</button>`);
|
|
105
|
+
}
|
|
73
106
|
/**
|
|
74
107
|
* Render one full stage section: heading + ornaments + count + rows.
|
|
75
108
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
109
|
+
* The output is wrapped in a `.er-stage-block` div that pairs a mobile-
|
|
110
|
+
* only stage tile (the collapsible head) with the existing section. On
|
|
111
|
+
* desktop, the tile is `display: none` and the section's `<h2>` head
|
|
112
|
+
* carries the heading as before. On mobile, the section's head is hidden
|
|
113
|
+
* and the tile is shown; tapping the tile toggles a `data-collapsed`
|
|
114
|
+
* attribute on the section that hides/shows its rows. Single-expand
|
|
115
|
+
* (tapping one tile collapses the others) is handled by
|
|
116
|
+
* `dashboard/stage-tiles.ts`.
|
|
117
|
+
*
|
|
118
|
+
* Empty stages still render their tile (so the pipeline shape is visible
|
|
119
|
+
* at-rest on phone) but the empty section body itself is hidden on mobile.
|
|
120
|
+
*
|
|
121
|
+
* Empty stages on desktop render compact (just the heading, no placeholder
|
|
122
|
+
* body) — keeps the operator's sense of pipeline shape without padding
|
|
123
|
+
* the dashboard with multi-line empty placeholders for low-volume
|
|
79
124
|
* calendars (#112). The hover title still surfaces the stage's
|
|
80
125
|
* "what to run next" hint when the operator points at the heading.
|
|
81
126
|
*/
|
|
82
127
|
export function renderStageSection(stage, entries, defaultSite) {
|
|
128
|
+
const tile = renderStageTile(stage, entries.length);
|
|
83
129
|
if (entries.length === 0) {
|
|
84
130
|
return unsafe(html `
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
131
|
+
<div class="er-stage-block" data-stage-block="${stage}">
|
|
132
|
+
${tile}
|
|
133
|
+
<section class="er-section er-section--empty"
|
|
134
|
+
id="stage-${stage.toLowerCase()}" data-stage-section="${stage}"
|
|
135
|
+
data-empty-stage="${stage}">
|
|
136
|
+
<h2 class="er-section-head er-section-head--empty"
|
|
137
|
+
title="${STAGE_EMPTY_MESSAGES[stage]}">
|
|
138
|
+
<span>${stage}</span>
|
|
139
|
+
<span class="ornament">${STAGE_ORNAMENTS[stage]}</span>
|
|
140
|
+
<span class="count">№ 00</span>
|
|
141
|
+
</h2>
|
|
142
|
+
</section>
|
|
143
|
+
</div>`);
|
|
95
144
|
}
|
|
96
145
|
const body = unsafe(entries.map((e, i) => renderRow(e, i, defaultSite).__raw).join(''));
|
|
97
146
|
return unsafe(html `
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
<div class="er-stage-block" data-stage-block="${stage}">
|
|
148
|
+
${tile}
|
|
149
|
+
<section class="er-section" id="stage-${stage.toLowerCase()}" data-stage-section="${stage}">
|
|
150
|
+
<h2 class="er-section-head">
|
|
151
|
+
<span>${stage}</span>
|
|
152
|
+
<span class="ornament">${STAGE_ORNAMENTS[stage]}</span>
|
|
153
|
+
<span class="count">№ ${entries.length}</span>
|
|
154
|
+
</h2>
|
|
155
|
+
${body}
|
|
156
|
+
</section>
|
|
157
|
+
</div>`);
|
|
106
158
|
}
|
|
107
159
|
/**
|
|
108
|
-
* Render the reserved Distribution placeholder.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
160
|
+
* Render the reserved Distribution placeholder. Distribution isn't a
|
|
161
|
+
* pipeline stage in the formal sense (no entries flow through it; it
|
|
162
|
+
* lives under its own model when shortform cross-posts arrive), but on
|
|
163
|
+
* the mobile dashboard it renders as a stage tile alongside the rest
|
|
164
|
+
* so the operator's pipeline-shape scan stays uniform — see operator
|
|
165
|
+
* feedback on 2026-05-09. The tile is `is-empty` + `disabled` until
|
|
166
|
+
* DistributionRecords land in the data layer.
|
|
167
|
+
*
|
|
168
|
+
* On desktop, the existing section + heading + placeholder text render
|
|
169
|
+
* as before; the tile is `display: none` per dashboard-mobile.css.
|
|
112
170
|
*/
|
|
113
171
|
export function renderDistributionPlaceholder() {
|
|
114
172
|
return unsafe(html `
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
173
|
+
<div class="er-stage-block" data-stage-block="Distribution">
|
|
174
|
+
<button class="er-stage-tile is-empty" type="button"
|
|
175
|
+
data-stage-tile="Distribution"
|
|
176
|
+
aria-expanded="false"
|
|
177
|
+
aria-controls="stage-distribution" disabled>
|
|
178
|
+
<span class="er-stage-tile-glyph" aria-hidden="true">⌘</span>
|
|
179
|
+
<span class="er-stage-tile-name">Distribution</span>
|
|
180
|
+
<span class="er-stage-tile-count"><span class="num">0</span></span>
|
|
181
|
+
<span class="er-stage-tile-chev" aria-hidden="true">›</span>
|
|
182
|
+
</button>
|
|
183
|
+
<section class="er-section" id="stage-distribution" data-stage-section="Distribution">
|
|
184
|
+
<h2 class="er-section-head">
|
|
185
|
+
<span>Distribution</span>
|
|
186
|
+
<span class="ornament">⌘</span>
|
|
187
|
+
</h2>
|
|
188
|
+
<div class="er-empty" style="padding: 1rem 0.25rem; font-size: 0.95rem;">
|
|
189
|
+
Reserved for shortform DistributionRecords — separate model.
|
|
190
|
+
</div>
|
|
191
|
+
</section>
|
|
192
|
+
</div>`);
|
|
124
193
|
}
|
|
125
194
|
function formatDate(iso) {
|
|
126
195
|
// Trim to YYYY-MM-DD for compact display. Full timestamp is on the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section.js","sourceRoot":"","sources":["../../../src/pages/dashboard/section.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"section.js","sourceRoot":"","sources":["../../../src/pages/dashboard/section.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single-stage section renderer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* with a section heading (stage name + entry count) and either
|
|
6
|
-
* of rows or an empty-state placeholder.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Each of the eight stage sections (plus the Distribution placeholder)
|
|
5
|
+
* renders with a section heading (stage name + entry count) and either
|
|
6
|
+
* a list of rows or an empty-state placeholder. Each row carries the
|
|
7
|
+
* entry's slug, title, updated-at timestamp, and stage-gated verb
|
|
8
|
+
* buttons. Per DESKWORK-STATE-MACHINE.md Commandment III, rows do NOT
|
|
9
|
+
* surface iteration counts or reviewState — those were retired in
|
|
10
|
+
* v0.19 along with the legacy reviewState concept.
|
|
11
|
+
*
|
|
12
|
+
* On mobile, each section is fronted by a collapsible tile (see
|
|
13
|
+
* `renderStageTile`); on desktop the tiles are display:none and the
|
|
14
|
+
* `<h2 class="er-section-head">` heading carries the stage name.
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
import { html, unsafe, type RawHtml } from '../html.ts';
|
|
13
18
|
import type { Entry, Stage } from '@deskwork/core/schema/entry';
|
|
14
|
-
import {
|
|
15
|
-
iterationForCurrentStage,
|
|
16
|
-
renderReviewStateBadge,
|
|
17
|
-
renderRowActions,
|
|
18
|
-
} from './affordances.ts';
|
|
19
|
+
import { renderRowActions } from './affordances.ts';
|
|
19
20
|
|
|
20
21
|
const STAGE_ORNAMENTS: Record<Stage, string> = {
|
|
21
22
|
Ideas: '◇',
|
|
@@ -43,13 +44,16 @@ const STAGE_EMPTY_MESSAGES: Record<Stage, string> = {
|
|
|
43
44
|
* Render one entry as a single dashboard row. Carries inline:
|
|
44
45
|
* - slug (linked to the review surface)
|
|
45
46
|
* - title
|
|
46
|
-
* - iteration count for the entry's currentStage
|
|
47
|
-
* - reviewState badge (or an em-dash placeholder)
|
|
48
47
|
* - updatedAt timestamp
|
|
49
48
|
* - per-stage action buttons
|
|
49
|
+
*
|
|
50
|
+
* Per DESKWORK-STATE-MACHINE.md (v5): revisions (the iteration counter)
|
|
51
|
+
* are bookkeeping and do NOT surface in routine UI. The previous
|
|
52
|
+
* "iteration: N" inline display was a violation — operators see
|
|
53
|
+
* revisions only via the View History surface and revert flows.
|
|
54
|
+
* reviewState badges are likewise retired (Commandment III).
|
|
50
55
|
*/
|
|
51
56
|
export function renderRow(entry: Entry, index: number, defaultSite: string): RawHtml {
|
|
52
|
-
const iteration = iterationForCurrentStage(entry);
|
|
53
57
|
const reviewLink = `/dev/editorial-review/entry/${entry.uuid}`;
|
|
54
58
|
const search = [entry.slug, entry.title, entry.keywords.join(' ')].join(' ').toLowerCase();
|
|
55
59
|
// Hierarchical entries (slugs containing `/`) get a visual indent
|
|
@@ -69,25 +73,63 @@ export function renderRow(entry: Entry, index: number, defaultSite: string): Raw
|
|
|
69
73
|
<span class="er-row-slug"><a href="${reviewLink}"
|
|
70
74
|
title="open the review surface">${entry.slug}</a></span>
|
|
71
75
|
<span class="er-calendar-title">${entry.title}</span>
|
|
72
|
-
<span class="er-calendar-meta er-calendar-meta-iteration"
|
|
73
|
-
data-iteration="${iteration}">iteration: ${iteration}</span>
|
|
74
76
|
<time class="er-calendar-meta er-calendar-meta-updated" data-format="date"
|
|
75
77
|
datetime="${entry.updatedAt}" title="${entry.updatedAt}">${formatDate(entry.updatedAt)}</time>
|
|
76
78
|
</div>
|
|
77
|
-
<span class="er-calendar-status"
|
|
78
|
-
title="open the review surface for ${entry.slug}"
|
|
79
|
-
class="er-stamp-link">${renderReviewStateBadge(entry.reviewState)}</a></span>
|
|
79
|
+
<span class="er-calendar-status" aria-hidden="true"></span>
|
|
80
80
|
${renderRowActions(entry, defaultSite)}
|
|
81
81
|
</div>
|
|
82
82
|
</div>`);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Render the stage tile (mobile-only collapsible head). Hidden on desktop
|
|
87
|
+
* via dashboard-mobile.css; the existing `<h2 class="er-section-head">`
|
|
88
|
+
* carries the head on desktop and is hidden at <=600px so the tile takes
|
|
89
|
+
* over.
|
|
90
|
+
*
|
|
91
|
+
* Empty stages render the same tile shape but with `is-empty` styling and
|
|
92
|
+
* `disabled` so taps are no-ops (operator can still SEE the empty stage
|
|
93
|
+
* in the pipeline shape — they just can't drill in to nothing).
|
|
94
|
+
*
|
|
95
|
+
* Review-state sub-counts (e.g. "5 · 3 in review") were removed in v0.19
|
|
96
|
+
* per operator: review state isn't user-facing data and is slated for
|
|
97
|
+
* backend removal; the tile shows total entry count only.
|
|
98
|
+
*/
|
|
99
|
+
function renderStageTile(stage: Stage, count: number): RawHtml {
|
|
100
|
+
const isEmpty = count === 0;
|
|
101
|
+
const classes = isEmpty ? 'er-stage-tile is-empty' : 'er-stage-tile';
|
|
102
|
+
const disabledAttr = isEmpty ? ' disabled' : '';
|
|
103
|
+
return unsafe(html`
|
|
104
|
+
<button class="${classes}" type="button"
|
|
105
|
+
data-stage-tile="${stage}"
|
|
106
|
+
aria-expanded="false"
|
|
107
|
+
aria-controls="stage-${stage.toLowerCase()}"${unsafe(disabledAttr)}>
|
|
108
|
+
<span class="er-stage-tile-glyph" aria-hidden="true">${STAGE_ORNAMENTS[stage]}</span>
|
|
109
|
+
<span class="er-stage-tile-name">${stage}</span>
|
|
110
|
+
<span class="er-stage-tile-count"><span class="num">${count}</span></span>
|
|
111
|
+
<span class="er-stage-tile-chev" aria-hidden="true">›</span>
|
|
112
|
+
</button>`);
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
/**
|
|
86
116
|
* Render one full stage section: heading + ornaments + count + rows.
|
|
87
117
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
118
|
+
* The output is wrapped in a `.er-stage-block` div that pairs a mobile-
|
|
119
|
+
* only stage tile (the collapsible head) with the existing section. On
|
|
120
|
+
* desktop, the tile is `display: none` and the section's `<h2>` head
|
|
121
|
+
* carries the heading as before. On mobile, the section's head is hidden
|
|
122
|
+
* and the tile is shown; tapping the tile toggles a `data-collapsed`
|
|
123
|
+
* attribute on the section that hides/shows its rows. Single-expand
|
|
124
|
+
* (tapping one tile collapses the others) is handled by
|
|
125
|
+
* `dashboard/stage-tiles.ts`.
|
|
126
|
+
*
|
|
127
|
+
* Empty stages still render their tile (so the pipeline shape is visible
|
|
128
|
+
* at-rest on phone) but the empty section body itself is hidden on mobile.
|
|
129
|
+
*
|
|
130
|
+
* Empty stages on desktop render compact (just the heading, no placeholder
|
|
131
|
+
* body) — keeps the operator's sense of pipeline shape without padding
|
|
132
|
+
* the dashboard with multi-line empty placeholders for low-volume
|
|
91
133
|
* calendars (#112). The hover title still surfaces the stage's
|
|
92
134
|
* "what to run next" hint when the operator points at the heading.
|
|
93
135
|
*/
|
|
@@ -96,50 +138,75 @@ export function renderStageSection(
|
|
|
96
138
|
entries: readonly Entry[],
|
|
97
139
|
defaultSite: string,
|
|
98
140
|
): RawHtml {
|
|
141
|
+
const tile = renderStageTile(stage, entries.length);
|
|
142
|
+
|
|
99
143
|
if (entries.length === 0) {
|
|
100
144
|
return unsafe(html`
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
145
|
+
<div class="er-stage-block" data-stage-block="${stage}">
|
|
146
|
+
${tile}
|
|
147
|
+
<section class="er-section er-section--empty"
|
|
148
|
+
id="stage-${stage.toLowerCase()}" data-stage-section="${stage}"
|
|
149
|
+
data-empty-stage="${stage}">
|
|
150
|
+
<h2 class="er-section-head er-section-head--empty"
|
|
151
|
+
title="${STAGE_EMPTY_MESSAGES[stage]}">
|
|
152
|
+
<span>${stage}</span>
|
|
153
|
+
<span class="ornament">${STAGE_ORNAMENTS[stage]}</span>
|
|
154
|
+
<span class="count">№ 00</span>
|
|
155
|
+
</h2>
|
|
156
|
+
</section>
|
|
157
|
+
</div>`);
|
|
111
158
|
}
|
|
112
159
|
|
|
113
160
|
const body = unsafe(entries.map((e, i) => renderRow(e, i, defaultSite).__raw).join(''));
|
|
114
161
|
|
|
115
162
|
return unsafe(html`
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
163
|
+
<div class="er-stage-block" data-stage-block="${stage}">
|
|
164
|
+
${tile}
|
|
165
|
+
<section class="er-section" id="stage-${stage.toLowerCase()}" data-stage-section="${stage}">
|
|
166
|
+
<h2 class="er-section-head">
|
|
167
|
+
<span>${stage}</span>
|
|
168
|
+
<span class="ornament">${STAGE_ORNAMENTS[stage]}</span>
|
|
169
|
+
<span class="count">№ ${entries.length}</span>
|
|
170
|
+
</h2>
|
|
171
|
+
${body}
|
|
172
|
+
</section>
|
|
173
|
+
</div>`);
|
|
124
174
|
}
|
|
125
175
|
|
|
126
176
|
/**
|
|
127
|
-
* Render the reserved Distribution placeholder.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
177
|
+
* Render the reserved Distribution placeholder. Distribution isn't a
|
|
178
|
+
* pipeline stage in the formal sense (no entries flow through it; it
|
|
179
|
+
* lives under its own model when shortform cross-posts arrive), but on
|
|
180
|
+
* the mobile dashboard it renders as a stage tile alongside the rest
|
|
181
|
+
* so the operator's pipeline-shape scan stays uniform — see operator
|
|
182
|
+
* feedback on 2026-05-09. The tile is `is-empty` + `disabled` until
|
|
183
|
+
* DistributionRecords land in the data layer.
|
|
184
|
+
*
|
|
185
|
+
* On desktop, the existing section + heading + placeholder text render
|
|
186
|
+
* as before; the tile is `display: none` per dashboard-mobile.css.
|
|
131
187
|
*/
|
|
132
188
|
export function renderDistributionPlaceholder(): RawHtml {
|
|
133
189
|
return unsafe(html`
|
|
134
|
-
<
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
<div class="er-stage-block" data-stage-block="Distribution">
|
|
191
|
+
<button class="er-stage-tile is-empty" type="button"
|
|
192
|
+
data-stage-tile="Distribution"
|
|
193
|
+
aria-expanded="false"
|
|
194
|
+
aria-controls="stage-distribution" disabled>
|
|
195
|
+
<span class="er-stage-tile-glyph" aria-hidden="true">⌘</span>
|
|
196
|
+
<span class="er-stage-tile-name">Distribution</span>
|
|
197
|
+
<span class="er-stage-tile-count"><span class="num">0</span></span>
|
|
198
|
+
<span class="er-stage-tile-chev" aria-hidden="true">›</span>
|
|
199
|
+
</button>
|
|
200
|
+
<section class="er-section" id="stage-distribution" data-stage-section="Distribution">
|
|
201
|
+
<h2 class="er-section-head">
|
|
202
|
+
<span>Distribution</span>
|
|
203
|
+
<span class="ornament">⌘</span>
|
|
204
|
+
</h2>
|
|
205
|
+
<div class="er-empty" style="padding: 1rem 0.25rem; font-size: 0.95rem;">
|
|
206
|
+
Reserved for shortform DistributionRecords — separate model.
|
|
207
|
+
</div>
|
|
208
|
+
</section>
|
|
209
|
+
</div>`);
|
|
143
210
|
}
|
|
144
211
|
|
|
145
212
|
function formatDate(iso: string): string {
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Studio dashboard page — `/dev/editorial-studio`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* The dashboard renders eight stage sections — Ideas → Planned →
|
|
5
|
+
* Outlining → Drafting → Final → Published, plus Blocked and
|
|
6
|
+
* Cancelled — backed by sidecar reads under
|
|
7
|
+
* `<projectRoot>/.deskwork/entries/*.json`, with a Distribution
|
|
8
|
+
* placeholder pinned at the end. Each row carries the entry's slug,
|
|
9
|
+
* title, updated-at timestamp, and stage-gated verb buttons that
|
|
10
|
+
* clipboard-copy `/deskwork:<verb> <slug>` (THESIS Consequence 2 —
|
|
11
|
+
* the studio routes commands; skills do the work). On phone (≤600px)
|
|
12
|
+
* each stage section is collapsed by default and fronted by a tile
|
|
13
|
+
* (see Compact-1 in DESIGN-STANDARDS.md); on desktop everything is
|
|
14
|
+
* expanded with the existing `<h2 class="er-section-head">` heading
|
|
15
|
+
* carrying the stage name.
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
17
|
+
* Per DESKWORK-STATE-MACHINE.md (Commandment III), reviewState is
|
|
18
|
+
* RETIRED. Rows do NOT carry per-stage iteration counts or
|
|
19
|
+
* reviewState badges; that legacy "at-a-glance" surfacing was
|
|
20
|
+
* removed in v0.19.
|
|
15
21
|
*
|
|
16
22
|
* The renderer's data flow:
|
|
17
23
|
* 1. loadDashboardData reads every sidecar and groups by stage.
|
|
18
|
-
* 2. Each
|
|
19
|
-
* 3. The Distribution placeholder
|
|
24
|
+
* 2. Each stage renders via `renderStageSection`.
|
|
25
|
+
* 3. The Distribution placeholder renders below the stage sections.
|
|
26
|
+
* 4. The mobile-only Compose chrome (FAB + slide-up sheet) renders
|
|
27
|
+
* at the page tail; CSS hides it on desktop.
|
|
20
28
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the new dashboard does not currently consume it (sidecars are the
|
|
25
|
-
* data source, not the on-disk content tree).
|
|
29
|
+
* `getIndex` is preserved for signature compatibility with the
|
|
30
|
+
* override resolver in server.ts; the dashboard does not consume it
|
|
31
|
+
* (sidecars are the data source, not the on-disk content tree).
|
|
26
32
|
*/
|
|
27
33
|
import type { StudioContext } from '../routes/api.ts';
|
|
28
34
|
import type { ContentIndex } from '@deskwork/core/content-index';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/pages/dashboard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/pages/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAUtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,YAAY,CAAC;AAElE;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,aAAa,EAClB,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,OAAO,CAAC,MAAM,CAAC,CA0CjB"}
|