create-pmos 0.86.0 → 0.88.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.86.0",
3
+ "version": "0.88.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@f91d32d
1
+ kernel-source: pmos@57df07b
2
2
  assembled: 2026-10-07
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-10-07T16:27:16Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T17:03:38Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,13 +2,15 @@
2
2
  type: Guide
3
3
  title: "Board: the items of every initiative"
4
4
  description: "How to read and use the Board destination, filter it, add an item, move a card, release a batch of work items, and print the same Board with pmos board."
5
- timestamp: 2026-10-05
5
+ timestamp: 2026-10-07
6
6
  ---
7
7
 
8
8
  # Board: the items of every initiative
9
9
 
10
10
  The Board shows the items of all your work initiatives in one place. It has four columns: Todo, Doing, Review and Done. A card is one item. The Board is a view of the same items that the Breakdown of each initiative shows, so a card is always in the column that its row has in the Tree.
11
11
 
12
+ The Board looks like the Agenda. The title at the top counts the items, for example "43 items in code". The filters and **Add item** sit on the right of the title. Each column heading has a status dot and a count. A column shows its first 3 cards. Press **Show 14 more** under a column to see all of its cards. An owner icon on a card shows who owns it: a robot for an agent, or "You".
13
+
12
14
  A document initiative has no items, so it has no card and no place in the filter.
13
15
 
14
16
  ## Open the Board
@@ -33,7 +35,7 @@ Press **Add item**, the one main button of the Board. Type a title, choose the i
33
35
 
34
36
  - **On a wide screen,** drag a card to another column. A drop on Done is held for 8 seconds, so you can press **Undo**.
35
37
  - **On a phone,** press the card to open its sheet and press a state button. A card moves in 2 taps. From another column it takes 3: first press the column name, then the card, then the state.
36
- - **From the menu.** Press the three dots on a card, then choose Move to a state.
38
+ - **From the menu.** Press the three dots on a card, then choose Move to a state. Every card shows its three dots.
37
39
 
38
40
  When you move a card that an agent owns, the card reads "moved by you". The agent's own next move can change it again.
39
41
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: Guide
3
3
  title: "Docs: every repository file by category, the files each one links to, and whether each copy in Google Docs or Confluence is the same"
4
- timestamp: 2026-10-06
4
+ timestamp: 2026-10-07
5
5
  ---
6
6
 
7
7
  # Docs
@@ -14,6 +14,8 @@ Nothing syncs by itself. A copy changes only when you press a button for that co
14
14
 
15
15
  ## The list
16
16
 
17
+ The title at the top says how many copies need you, for example "2 need you in pmos". The three views are a segmented control at the top right, each with a count. Under the title, one bar shows when the copies were last compared, a search box and the **Compare every copy again** button. The list is in one card with a small label that names the view.
18
+
17
19
  Docs has three views:
18
20
 
19
21
  - **Needs you** shows the copies that are not the same.
@@ -117,7 +119,7 @@ A sync never creates, moves or deletes a page or a file. When the copy's text is
117
119
 
118
120
  ## Compare
119
121
 
120
- PMOS cannot read Google Docs or Confluence itself. **↻** next to "Compared … ago" starts a session that reads every copy with your own connectors. The session reads only. The sentences change when it finishes.
122
+ PMOS cannot read Google Docs or Confluence itself. **Compare every copy again** in the bar, next to "Compared … ago", starts a session that reads every copy with your own connectors. The session reads only. The sentences change when it finishes.
121
123
 
122
124
  A compare runs only when you press it. There is no schedule. An edit to a repository file changes its sentence at once, without a compare.
123
125
 
@@ -50,7 +50,7 @@ The dock holds your terminals, documents, conversations, skills and web pages. I
50
50
 
51
51
  - Press ⌘J (Ctrl-J), or the button on the rail, to open the dock. Press ⌘J again, or the fold button in the dock, to fold it to the rail.
52
52
  - The dock never opens by itself. A tab that opens while the dock is a rail waits in the dock until you open it.
53
- - The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (started by the workspace itself, for example by a schedule or the day run). **Automatic** is hidden until you press **Show automatic** at the foot of the list (or beside the group select in the strip). A session in front, or one that waits on you, is always shown. Finished automatic sessions remove themselves a minute after a clean exit, and one with no output for 60 minutes is ended and removed. A failed one stays. The setting is in `state/session-policy.json`: `finish` (true or false) and `idleMinutes` (5 or more, or 0 for off). Then **Open** lists the tabs that are not sessions, for example documents and skills. Each one shows its title, what it is (for example PRD, Document or Web page) and its branch when it is not main. **✕** on an Open row closes the tab only. A terminal keeps running.
53
+ - The dock lists the sessions of the repository by initiative. A session is in the group of the initiative it is recorded on, or of its `initiative/<id>` branch. Then come **Not linked** (no initiative) and **Automatic** (anything you did not start yourself, for example a schedule, the day run, a note's summary or bullets, or a Jira push). **Automatic** is hidden until you press **Show automatic** at the foot of the list (or beside the group select in the strip). A session in front, or one held on a permission question, is always shown. An agent that has finished and reads "needs you" is hidden like the rest. Finished automatic sessions remove themselves a minute after a clean exit, and one with no output for 60 minutes is ended and removed. A failed one stays. The setting is in `state/session-policy.json`: `finish` (true or false) and `idleMinutes` (5 or more, or 0 for off). Then **Open** lists the tabs that are not sessions, for example documents and skills. Each one shows its title, what it is (for example PRD, Document or Web page) and its branch when it is not main. **✕** on an Open row closes the tab only. A terminal keeps running.
54
54
  - Each session row starts with a mark for its state: ● working, ◼ quiet, ◆ needs you, ◇ maybe blocked, ○ ended. Under the name, the row shows the state when the session is not working, the type of work when one is set, and the branch when it is not main and not the initiative's own branch. Running sessions come first and ended sessions last. Press the row to bring the session to the front.
55
55
  - Press the name of a group to fold it or open it. The number beside it counts its sessions. **Automatic** is folded while none of its sessions is running, because each run leaves an ended session. The group of the session in front always stays open, and the workspace keeps your choice for each group.
56
56
  - **⋯** and **✕** show on the row in front, on the row under the pointer and on the row with the keyboard focus. On a touch screen they always show.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  type: Guide
3
3
  title: "People: the people you work with in a repository, their interactions, tone, notes and shared work"
4
- timestamp: 2026-10-06
4
+ timestamp: 2026-10-07
5
5
  ---
6
6
 
7
7
  # People
@@ -37,16 +37,18 @@ A sighting of a new handle adds the person. A sighting with the full name of a p
37
37
 
38
38
  ## The list
39
39
 
40
- Type in **Find a person** to search names, handles and initiatives. **All**, **Team** and **External** filter the list. **Latest**, **Most** and **Name** sort it by the last interaction, the number of interactions or the name. Press a person to open their page beside the list.
40
+ People has the same two-card layout as the Agenda. The title says how many people there are. **All**, **Team** and **External** at the top right filter the list. The left card holds the list. Type in **Find a person** to search names, handles and initiatives. **Latest**, **Most** and **Name** sort it by the last interaction, the number of interactions or the name. Each row shows initials, the name, the role and organisation, and the tone as a coloured dot (a ? means you have not confirmed it). Press a person to open their page in the right card. Each card scrolls by itself.
41
41
 
42
42
  ## A person's page
43
43
 
44
+ The top of the page shows the person's name and role, a **Confirm** button when a tone is waiting, and a **⋯** menu. Three tiles under it give the number of interactions, the tone and the shared work.
45
+
44
46
  - **Interactions.** Newest first. Each line shows the date, the kind (Mail, Chat, Meeting, 1:1, Jira, Doc, Call or Mention), the line, and where it came from with its pointer and tone.
45
- - **Tone.** The mix of tones (warm, neutral, cautious, tense) and a proposed overall tone: the most frequent tone of the latest 10 interactions. When two tones are tied, the latest one wins. Press **Confirm** to keep the proposal, **Set another** to choose a different tone, or **Change** to clear your choice. Tone describes the exchanges, not the person.
47
+ - **Tone.** The mix of tones (warm, neutral, cautious, tense) and a proposed overall tone: the most frequent tone of the latest 10 interactions. When two tones are tied, the latest one wins. Press **Confirm** (at the top or here) to keep the proposal, **Set another** to choose a different tone, or **Change** to clear your choice. Tone describes the exchanges, not the person.
46
48
  - **Shared work.** The initiatives your interactions name, the plan items the person owns (with **Blocked** when the item is blocked), and the coming meetings that name them when calendar names are on. Nothing here is typed by hand.
47
- - **Notes.** Type a note and press **Add note**. Press **Remove** to remove one. Only you can add or remove a note. No command writes one.
49
+ - **Notes.** Type a note and press the **+** button (**Add note**). Press the **×** next to a note to remove it. Only you can add or remove a note. No command writes one.
48
50
  - **Identity and sources.** The person's handles, how many interactions each source gave, where People found them, and the file the person lives in.
49
- - **Remove from People.** Takes the person out. Later sightings of their handles stay out.
51
+ - **Remove from People.** In the **⋯** menu. Takes the person out. Later sightings of their handles stay out.
50
52
 
51
53
  ## Ways in from other places
52
54
 
@@ -16,6 +16,8 @@ Above the cards, Today shows the day in two columns.
16
16
 
17
17
  Press **Prepare my day** to start the day. It runs from the brief to the delivery of each agent job, and you do not press anything else until a draft is ready for you. The page [Guide: Today and the proactive engine](proactive-engine.md) describes each step.
18
18
 
19
+ The bar at the top of the day is one line. It shows the greeting, the date and how many goals are unfinished. While **Prepare my day** runs, it also shows the four steps (Read, Sort, Agents work, You approve) as a small progress bar; press a step to see its detail. When nothing runs, it says when the day was last prepared. **Sources** and **Check now** are icon buttons in the bar.
20
+
19
21
  If the morning-brief session stops before it submits a brief, the Read step says so and gives the reason. **Prepare my day** is then ready again, so press it to try once more. If the session says that its folder is not there, open the repository again from its current folder first.
20
22
 
21
23
  Each agent job runs in its own session, so you can open any job and follow or steer it. The workspace types the job's prompt into the session when the agent is ready to read, and presses Enter for you. When you approve or revise a job and its session is still open, the workspace uses that same session again. Prepare again works the same way: when the morning-brief session is still open and has finished its read, or has printed nothing for two minutes, the workspace types the read into that session and runs it again.
@@ -6,17 +6,22 @@
6
6
  * be done to them. Pure: the server passes in the rows and the clock, so the rules are testable without a server.
7
7
  *
8
8
  * Next step, Hand to agent, a skill draft and an alignment are NOT automatic: the PM pressed those, and often talks on.
9
+ * Any other origin is automatic.
9
10
  */
10
11
  'use strict';
11
12
 
12
- const AUTOMATIC_EXACT = Object.freeze(['day-task', 'day-check', 'my-work-read', 'docs:sync', 'docs:check']);
13
- const AUTOMATIC_PREFIX = Object.freeze(['schedule:']);
13
+ // The rule is "if the PM did not trigger it, it is automatic", so the list names what the PM DID trigger and everything
14
+ // else with an origin is automatic. A new background job is then hidden and cleaned up without anyone remembering to list it.
15
+ // Manual: a session opened with no origin, a Next step or Hand to agent press, a skill draft, an alignment, and the Whisper
16
+ // and final-pass setup shells (the PM pressed those and watches them install).
17
+ const MANUAL_EXACT = Object.freeze(['next-step', 'plan-item', 'notes-whisper-setup', 'notes-final-setup']);
18
+ const MANUAL_PREFIX = Object.freeze(['skill-draft:', 'align:']);
14
19
 
15
- /** True when the workspace, not the PM, started a session with this origin. */
20
+ /** True when the PM did not trigger a session with this origin: a schedule, the day run, a doc sync, a note's passes, Jira. */
16
21
  function isAutomatic(origin) {
17
22
  const o = String(origin || '');
18
23
  if (!o) return false;
19
- return AUTOMATIC_EXACT.includes(o) || AUTOMATIC_PREFIX.some((p) => o.startsWith(p));
24
+ return !(MANUAL_EXACT.includes(o) || MANUAL_PREFIX.some((p) => o.startsWith(p)));
20
25
  }
21
26
 
22
27
  const DEFAULT_POLICY = Object.freeze({ finish: true, idleMinutes: 60 });
@@ -57,9 +62,10 @@ function decide(s, policy, now) {
57
62
  return age >= FINISH_GRACE_MS ? 'remove' : null;
58
63
  }
59
64
  if (!policy.idleMinutes) return null;
60
- // waiting on a person is not idle: the session is asking something
61
- if (s.shown === 'needs' || s.shown === 'blocked') return null;
65
+ // A call waiting for permission is a real ask. 'needs' (the agent's turn is over) is NOT: a finished agent always reads
66
+ // 'needs you', so exempting it would keep every finished job for ever. The idle clock still gives a late question time.
67
+ if (s.shown === 'blocked') return null;
62
68
  return age >= policy.idleMinutes * 60 * 1000 ? 'end' : null;
63
69
  }
64
70
 
65
- module.exports = { isAutomatic, normalizePolicy, decide, endedCleanly, DEFAULT_POLICY, FINISH_GRACE_MS, AUTOMATIC_EXACT, AUTOMATIC_PREFIX };
71
+ module.exports = { isAutomatic, normalizePolicy, decide, endedCleanly, DEFAULT_POLICY, FINISH_GRACE_MS, MANUAL_EXACT, MANUAL_PREFIX };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.86.0",
3
+ "version": "0.88.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.86.0",
9
+ "version": "0.88.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.86.0",
3
+ "version": "0.88.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -10,10 +10,11 @@
10
10
  */
11
11
 
12
12
  import { el } from './dom.js';
13
- import { card, tabs, dragHandle, iconButton, empty } from './primitives.js';
13
+ import { card, tabs, dragHandle, iconButton, empty, button, badge } from './primitives.js';
14
14
  import { COPY, STATES, shortDate, ownerLabel } from './breakdown-copy.js';
15
15
  import { stageLabel } from './bd-model.js';
16
16
  import { signalsFor, byPart } from './bd-tree.js';
17
+ import { icon } from './icons.js';
17
18
 
18
19
  /** The width below which the Board shows one column at a time. Mirrors components.css's container query. */
19
20
  export const WIDE_BOARD = 600;
@@ -21,14 +22,25 @@ export const WIDE_BOARD = 600;
21
22
  function boardCard(ctx, id, wide) {
22
23
  const { L, A } = ctx;
23
24
  const it = L.items[id];
24
- const meta = [stageLabel(L, it.stage), ownerLabel(it.owner), it.due ? COPY.due(shortDate(it.due)) : null, byPart(it)];
25
- const trail = ctx.cardMenus
26
- ? [iconButton({
25
+ const ag = !!ctx.agenda;
26
+ // the Board destination shows the Agenda's card: initiative and stage only, the owner as an icon; the Breakdown keeps the long meta
27
+ const meta = ag ? [stageLabel(L, it.stage)] : [stageLabel(L, it.stage), ownerLabel(it.owner), it.due ? COPY.due(shortDate(it.due)) : null, byPart(it)];
28
+
29
+ const ownerIcon = !ag ? null : it.owner === 'you'
30
+ ? badge({ text: 'You', tone: 'outline', attrs: { 'data-bd-card-owner': 'you' } })
31
+ : it.owner === 'agent'
32
+ ? el('span', { class: 'bd-card__owner-icon', 'data-bd-card-owner': 'agent' }, icon('bot'))
33
+ : null;
34
+
35
+ const trail = !ag && !ctx.cardMenus ? null : [
36
+ ownerIcon,
37
+ iconButton({
27
38
  icon: 'ellipsis', size: 'sm', ariaLabel: COPY.aria.more(it.title),
28
39
  onClick: (e) => { e.stopPropagation(); A.menu(e.currentTarget, id, 'board'); },
29
- attrs: { 'aria-haspopup': 'menu', 'data-fk': 'more:' + id, 'data-bd-more': '' },
30
- })]
31
- : null;
40
+ attrs: { 'aria-haspopup': 'menu', 'data-fk': 'more:' + id, 'data-bd-more': '', 'data-bd-card-menu': '' },
41
+ }),
42
+ ].filter(Boolean);
43
+
32
44
  // Without the ⋯ button the card's menu is a right-click away, and Shift+F10 (or the ContextMenu key) for the keyboard.
33
45
  const menuKeys = ctx.cardMenus ? {} : {
34
46
  'aria-keyshortcuts': 'Shift+F10',
@@ -48,8 +60,9 @@ function boardCard(ctx, id, wide) {
48
60
  };
49
61
  return card({
50
62
  title: it.title, titleIcon: it.milestone ? 'diamond' : null, meta, signals: signalsFor(it, 'board'),
51
- lead: wide ? [dragHandle({ label: COPY.aria.reorder(it.title) })] : null, trail,
63
+ lead: wide ? [dragHandle({ label: COPY.aria.reorder(it.title), })] : null, trail,
52
64
  muted: it.proposed, onOpen: () => A.openSheet(id),
65
+ center: ag,
53
66
  attrs: { class: 'bd-card', 'data-item': id, 'data-state': it.state },
54
67
  openAttrs: { 'data-fk': 'card:' + id, ...menuKeys },
55
68
  });
@@ -68,14 +81,47 @@ export function renderBoard(ctx) {
68
81
  attrs: { class: 'bd-switch', 'data-bd-switch': '' },
69
82
  });
70
83
  for (const b of sw.querySelectorAll('[data-tab]')) b.setAttribute('data-fk', 'col:' + b.getAttribute('data-tab'));
71
- const cols = STATES.map((s) => el('div', { class: 'bd-col' + (s === shown ? ' bd-col--shown' : ''), 'data-col-wrap': s },
72
- el('div', { class: 'bd-col__head' }, el('span', null, COPY.states[s]), el('span', { class: 'bd-col__n' }, '· ' + counts[s])),
73
- s === 'done' && L.board.doneOlder > 0 ? el('div', { class: 'bd-col__older', 'data-bd-older': String(L.board.doneOlder) }, COPY.olderInTree(L.board.doneOlder)) : null,
74
- // an empty column says so, so a blank area never reads as a failed load
75
- !counts[s] && !(s === 'done' && L.board.doneOlder > 0) ? empty({ text: COPY.emptyColumn(COPY.states[s]), attrs: { 'data-bd-empty-col': s } }) : null,
76
- // the list fills its column, so the whole column height takes a drop
77
- el('div', { class: 'bd-col__list', 'data-col': s, 'aria-label': COPY.states[s] }, (L.board[s] || []).map((id) => boardCard(ctx, id, wide)))));
78
- return [sw, el('div', { class: 'bd-board', 'data-bd-board': wide ? 'wide' : 'narrow' }, cols)];
84
+
85
+ const cols = STATES.map((s) => {
86
+ const allIds = L.board[s] || [];
87
+ const expanded = P.expandedCols || (P.expandedCols = new Set());
88
+ const isExpanded = expanded.has(s);
89
+ const showLimit = ctx.agenda ? 3 : Infinity;
90
+ const shownIds = isExpanded ? allIds : allIds.slice(0, showLimit);
91
+ const hasMore = allIds.length > showLimit;
92
+ const moreCount = allIds.length - showLimit;
93
+
94
+ const statusDot = el('span', { class: 'bd-col__dot bd-col__dot--' + s });
95
+ const colName = COPY.states[s];
96
+ const countBadge = badge({ text: String(counts[s]), tone: 'outline', attrs: { 'data-bd-col-count': s } });
97
+
98
+ const cards = shownIds.map((id) => boardCard(ctx, id, wide));
99
+
100
+ const moreButton = hasMore && !isExpanded
101
+ ? button({
102
+ label: `Show ${moreCount} more`,
103
+ variant: 'ghost',
104
+ icon: 'chevron-down',
105
+ size: 'sm',
106
+ onClick: () => {
107
+ expanded.add(s);
108
+ A.rerender();
109
+ },
110
+ attrs: { 'data-bd-col-more': s, 'data-bd-show-more': '' },
111
+ })
112
+ : null;
113
+
114
+ return el('div', { class: 'bd-col' + (s === shown ? ' bd-col--shown' : ''), 'data-col-wrap': s },
115
+ ctx.agenda
116
+ ? el('div', { class: 'bd-col__head' }, el('div', { class: 'bd-col__name' }, statusDot, el('span', null, colName)), countBadge)
117
+ : el('div', { class: 'bd-col__head' }, el('span', null, COPY.states[s]), el('span', { class: 'bd-col__n' }, '· ' + counts[s])),
118
+ s === 'done' && L.board.doneOlder > 0 ? el('div', { class: 'bd-col__older', 'data-bd-older': String(L.board.doneOlder) }, COPY.olderInTree(L.board.doneOlder)) : null,
119
+ // an empty column says so, so a blank area never reads as a failed load
120
+ !counts[s] && !(s === 'done' && L.board.doneOlder > 0) ? empty({ text: COPY.emptyColumn(COPY.states[s]), attrs: { 'data-bd-empty-col': s } }) : null,
121
+ // the list fills its column, so the whole column height takes a drop
122
+ el('div', { class: 'bd-col__list', 'data-col': s, 'aria-label': COPY.states[s] }, ...cards, moreButton));
123
+ });
124
+ return [sw, el('div', { class: 'bd-board' + (ctx.agenda ? ' bd-board--agenda' : ''), 'data-bd-board': wide ? 'wide' : 'narrow' }, cols)];
79
125
  }
80
126
 
81
127
  /**
@@ -16,6 +16,7 @@ export const COPY = {
16
16
  initiative: 'Initiative',
17
17
  anyInitiative: 'All initiatives',
18
18
  addItem: 'Add item',
19
+ title: (n, productName) => `${n} ${n === 1 ? 'item' : 'items'} in ${productName || 'code'}`,
19
20
  adding: {
20
21
  title: 'Add item',
21
22
  sub: 'The item goes to its initiative. Enter adds it.',
@@ -34,6 +35,7 @@ export const COPY = {
34
35
  stageOf: (initiative, stage) => `${initiative} · ${stage}`,
35
36
  notMerged: 'Done · your call · not merged',
36
37
  summary: (n) => (n === 1 ? '1 item' : `${n} items`),
38
+ showMore: (n) => `Show ${n} more`,
37
39
  release: {
38
40
  head: 'Release',
39
41
  title: (initiative) => `Release · ${initiative}`,