@turri-dev/cli 0.1.4-next.4f471db → 0.1.4-next.5255d82
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/README.md +3 -3
- package/dist/extension.js +30 -8
- package/dist/picture-row.js +3 -2
- package/dist/regenerate/panel-dock.js +7 -5
- package/dist/screen-padding.js +72 -0
- package/docs/development.md +1 -0
- package/docs/images/turri-screenshot.png +0 -0
- package/docs/using.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,17 +101,17 @@ With **turri** you work on the durable intent. You say what the software must do
|
|
|
101
101
|
|
|
102
102
|
## How it works
|
|
103
103
|
|
|
104
|
+
<p align="center"><img src="docs/images/turri-screenshot.png" width="900" alt="turri in the terminal: on the left the conversation, where /regenerate plans a project's modules and a coding agent works through one of them; on the right the spec map of that module's ***plain, each functionality marked as regenerated or being worked on"></p>
|
|
105
|
+
|
|
104
106
|
**turri** captures the durable intent in [\*\*\*plain](https://plainlang.org/), a language for saying what software should do. \*\*\*plain breaks the intent down into functionalities - small units, each a single thing the software does - and **turri** regenerates the code from them piece by piece.
|
|
105
107
|
|
|
106
|
-
1. **Define.**
|
|
108
|
+
1. **Define.** You say what the software should do; **turri** writes it down as \*\*\*plain.
|
|
107
109
|
2. **Review and refine.** You review \*\*\*plain and change it with **turri** until it says what you mean.
|
|
108
110
|
3. **Regenerate.** Agents write the code; **turri** decides what they write and when, step by step, the same way every time.
|
|
109
111
|
4. **Validate.** Agents write conformance tests from \*\*\*plain; **turri** runs them, and keeps the code only when it does what \*\*\*plain says.
|
|
110
112
|
|
|
111
113
|
These are not phases, one after the other: you don't spend hours writing \*\*\*plain and then hours waiting for the code. You go round the loop in small steps, in conversation with **turri** - while you refine the next functionality, the one before it is already being regenerated and validated. A bug is fixed in \*\*\*plain; a new behaviour goes into \*\*\*plain; and the next regeneration carries it. Ask to try it at any time: **turri** puts what is built in `dist` and starts it on your machine - an app, and it tells you where to open your browser.
|
|
112
114
|
|
|
113
|
-
<p align="center"><img src="docs/images/turri-screenshot.png" width="900" alt="turri in the terminal: on the left the conversation, where /regenerate plans a project's modules and a coding agent works through one of them; on the right the spec map of that module's ***plain, each functionality marked as regenerated or being worked on"></p>
|
|
114
|
-
|
|
115
115
|
## The name
|
|
116
116
|
|
|
117
117
|
**turri** is short for [*Turritopsis dohrnii*](https://en.wikipedia.org/wiki/Turritopsis_dohrnii), the immortal jellyfish, which, when injured or old, turns back into its young self and regenerates - the same jellyfish, from the same genes, again and again.
|
package/dist/extension.js
CHANGED
|
@@ -34,6 +34,7 @@ import { halfBlocks, JELLYFISH_FRAME_MS, JELLYFISH_TEXT_FRAMES } from "./jellyfi
|
|
|
34
34
|
import { createJobPanel, didNotPass, keyPressed, PANEL_KEY, spinner } from "./regenerate/job-panel.js";
|
|
35
35
|
import { clickToFocus, dockedWidth, narrowChat, shade, shadeChat } from "./regenerate/panel-dock.js";
|
|
36
36
|
import { overPictureRow, PICTURE_AT } from "./picture-row.js";
|
|
37
|
+
import { padScreen, paddedColumns, paddedRows, SCREEN_PADDING } from "./screen-padding.js";
|
|
37
38
|
import { skillAboutLine, skillProgressLine } from "./skill-progress.js";
|
|
38
39
|
import { createSidePanel } from "./regenerate/side-panel.js";
|
|
39
40
|
import { createSpecMapSource, defaultMapFiles } from "./regenerate/spec-map.js";
|
|
@@ -97,7 +98,7 @@ aside) {
|
|
|
97
98
|
...(context !== undefined ? { context } : {}),
|
|
98
99
|
...(options !== undefined ? { options } : {}),
|
|
99
100
|
...(selected !== undefined ? { selected } : {}),
|
|
100
|
-
rows: () => tui.terminal.rows,
|
|
101
|
+
rows: () => paddedRows(tui.terminal.rows),
|
|
101
102
|
style: { accent: (text) => theme.fg("accent", text), muted: (text) => theme.fg("muted", text), bold: (text) => theme.bold(text) },
|
|
102
103
|
done: (answer) => {
|
|
103
104
|
if (questionShown === shown)
|
|
@@ -1527,7 +1528,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1527
1528
|
// The terminal the panel is drawn on, for its width: set as it opens.
|
|
1528
1529
|
let panelTerminal;
|
|
1529
1530
|
/** The columns the panel takes beside the chat - 0 over the whole screen, or when the terminal is too narrow to share. */
|
|
1530
|
-
const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(panelTerminal.columns) : 0);
|
|
1531
|
+
const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(paddedColumns(panelTerminal.columns)) : 0);
|
|
1531
1532
|
// What is open in the pulse list and the map, and where their cursors are: the conversation's, as left each time it opens.
|
|
1532
1533
|
const jobsTree = createJobsTreeState();
|
|
1533
1534
|
const mapState = createSpecMapState();
|
|
@@ -1585,7 +1586,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1585
1586
|
const layOut = () => {
|
|
1586
1587
|
if (panelDocked && undock === undefined) {
|
|
1587
1588
|
const widen = narrowChat(tui, panelBeside);
|
|
1588
|
-
const unwatch = clickToFocus(tui, () => tui.terminal.columns - panelBeside(), (side) => {
|
|
1589
|
+
const unwatch = clickToFocus(tui, () => tui.terminal.columns - SCREEN_PADDING.right - panelBeside(), (side) => {
|
|
1589
1590
|
if (panelBeside() === 0)
|
|
1590
1591
|
return;
|
|
1591
1592
|
if (side === "chat")
|
|
@@ -1626,7 +1627,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1626
1627
|
tree: jobsTree,
|
|
1627
1628
|
map: () => specMap.read(),
|
|
1628
1629
|
mapState,
|
|
1629
|
-
rows: () => tui.terminal.rows,
|
|
1630
|
+
rows: () => paddedRows(tui.terminal.rows),
|
|
1630
1631
|
key: PANEL_KEY,
|
|
1631
1632
|
dock: {
|
|
1632
1633
|
docked: () => panelBeside() > 0,
|
|
@@ -1757,14 +1758,15 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1757
1758
|
return component;
|
|
1758
1759
|
}, {
|
|
1759
1760
|
overlay: true,
|
|
1760
|
-
// The whole screen, or the columns on the right beside the chat - read at each frame, so it follows the terminal
|
|
1761
|
+
// The whole screen, or the columns on the right beside the chat - read at each frame, so it follows the terminal -
|
|
1762
|
+
// inside the screen's padding.
|
|
1761
1763
|
overlayOptions: {
|
|
1762
1764
|
get width() {
|
|
1763
1765
|
return panelBeside() > 0 ? panelBeside() : "100%";
|
|
1764
1766
|
},
|
|
1765
1767
|
maxHeight: "100%",
|
|
1766
1768
|
anchor: "top-right",
|
|
1767
|
-
margin:
|
|
1769
|
+
margin: { ...SCREEN_PADDING },
|
|
1768
1770
|
},
|
|
1769
1771
|
onHandle: (handle) => {
|
|
1770
1772
|
if (panel !== undefined)
|
|
@@ -2323,8 +2325,10 @@ function registerBranding(pi, skills) {
|
|
|
2323
2325
|
return new Banner(lines, (rule) => theme.fg("borderMuted", rule), jellyfishArt(theme.appearance, (theme.getColorMode?.() ?? "truecolor") === "truecolor"));
|
|
2324
2326
|
});
|
|
2325
2327
|
pi.on("session_start", (_event, ctx) => {
|
|
2326
|
-
if (ctx.hasUI)
|
|
2328
|
+
if (ctx.hasUI) {
|
|
2329
|
+
padTheScreen(ctx);
|
|
2327
2330
|
animateJellyfish(ctx);
|
|
2331
|
+
}
|
|
2328
2332
|
try {
|
|
2329
2333
|
const identity = turriIdentity();
|
|
2330
2334
|
pi.appendEntry(BANNER_ENTRY, {
|
|
@@ -2376,6 +2380,24 @@ function animateJellyfish(ctx) {
|
|
|
2376
2380
|
}, { placement: "belowEditor" });
|
|
2377
2381
|
}
|
|
2378
2382
|
const JELLYFISH_CLOCK_KEY = "turri-jellyfish";
|
|
2383
|
+
/**
|
|
2384
|
+
* The whole screen inside its padding (`screen-padding.ts`). An extension is given pi's screen only through what it
|
|
2385
|
+
* draws, so an empty widget below the editor hands it over - at each frame, as pi starts a new screen when it changes
|
|
2386
|
+
* how it draws.
|
|
2387
|
+
*/
|
|
2388
|
+
function padTheScreen(ctx) {
|
|
2389
|
+
ctx.ui.setWidget(SCREEN_PADDING_KEY, (tui) => {
|
|
2390
|
+
padScreen(tui);
|
|
2391
|
+
return {
|
|
2392
|
+
render: () => {
|
|
2393
|
+
padScreen(tui);
|
|
2394
|
+
return [];
|
|
2395
|
+
},
|
|
2396
|
+
invalidate: () => { },
|
|
2397
|
+
};
|
|
2398
|
+
}, { placement: "belowEditor" });
|
|
2399
|
+
}
|
|
2400
|
+
const SCREEN_PADDING_KEY = "turri-padding";
|
|
2379
2401
|
/** The columns the banner's picture takes from the left edge, while it shows one; 0 when it does not. */
|
|
2380
2402
|
let pictureRight = 0;
|
|
2381
2403
|
/**
|
|
@@ -2434,7 +2456,7 @@ class Banner {
|
|
|
2434
2456
|
return line !== undefined ? `${before}${line}` : placed?.kind === "text" ? ` ${jellyfish}` : "";
|
|
2435
2457
|
});
|
|
2436
2458
|
// The side panel is drawn beside the picture (drawOverlaysBesidePicture), which goes on showing.
|
|
2437
|
-
pictureRight = placed?.kind === "image" ? 1 + placed.columns : 0;
|
|
2459
|
+
pictureRight = placed?.kind === "image" ? SCREEN_PADDING.left + 1 + placed.columns : 0;
|
|
2438
2460
|
if (placed?.kind === "image") {
|
|
2439
2461
|
const at = placed.onLastRow ? placed.rows - 1 : 0;
|
|
2440
2462
|
rows[at] = `${rows[at]}${PICTURE_AT}${placed.sequence}`;
|
package/dist/picture-row.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compositeTuiLine } from "@earendil-works/pi-tui";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { SCREEN_PADDING } from "./screen-padding.js";
|
|
3
|
+
/** What the banner puts between a row's text and the picture after it: back to the jellyfish's column, a column in from the screen's padding. */
|
|
4
|
+
export const PICTURE_AT = `\x1b[${SCREEN_PADDING.left + 2}G`;
|
|
4
5
|
/** The picture's escape sequence, as the banner writes it after PICTURE_AT: Kitty's, in chunks, or iTerm2's - climbing first on iTerm2. */
|
|
5
6
|
const PICTURE = /^(?:\x1b\[\d+A)?(?:(?:\x1b_G[^\x1b]*\x1b\\)+|\x1b\]1337;File=[^\x07]*\x07)/;
|
|
6
7
|
/**
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { HStack } from "@earendil-works/pi-tui";
|
|
2
|
+
import { unpadded } from "../screen-padding.js";
|
|
2
3
|
/**
|
|
3
4
|
* The side panel beside the chat rather than over it: the chat drawn on the left in what the panel leaves it, the panel
|
|
4
5
|
* laid over the columns on the right.
|
|
5
6
|
*
|
|
6
7
|
* pi draws its screen one of two ways, and the chat is narrowed in each as it is drawn: full screen (pi's default), a
|
|
7
8
|
* layout - its root put in a row beside a filler as wide as the panel; the regular screen, the lines its render gives -
|
|
8
|
-
* asked for in fewer columns.
|
|
9
|
+
* asked for in fewer columns. Either way inside the screen's padding (`screen-padding.ts`), when it has one.
|
|
9
10
|
*/
|
|
10
11
|
/** Narrower than this, the chat and the panel have no room side by side: the panel takes the whole screen. */
|
|
11
12
|
const SHARED_FROM = 100;
|
|
12
|
-
/** The columns the panel takes beside the chat: half
|
|
13
|
+
/** The columns the panel takes beside the chat: half of what they share, the rule between them in the middle - 0 when that is too narrow to share. */
|
|
13
14
|
export function dockedWidth(columns) {
|
|
14
15
|
return columns < SHARED_FROM ? 0 : Math.floor(columns / 2);
|
|
15
16
|
}
|
|
@@ -20,14 +21,15 @@ export function dockedWidth(columns) {
|
|
|
20
21
|
export function narrowChat(tui, panelWidth) {
|
|
21
22
|
const screen = tui;
|
|
22
23
|
if (typeof screen.setLayoutRoot === "function" && screen.layoutRoot !== undefined) {
|
|
23
|
-
|
|
24
|
+
// What the padding pads: the row goes inside it, and is padded as it is set.
|
|
25
|
+
const root = unpadded(screen.layoutRoot);
|
|
24
26
|
// A line as wide as the panel: the row gives the filler what it measures, the chat the rest.
|
|
25
27
|
const filler = { render: () => [" ".repeat(panelWidth())], invalidate() { } };
|
|
26
28
|
screen.setLayoutRoot(new HStack([{ component: root, basis: 0, grow: 1 }, { component: filler }]));
|
|
27
29
|
return () => screen.setLayoutRoot(root);
|
|
28
30
|
}
|
|
29
|
-
//
|
|
30
|
-
const render =
|
|
31
|
+
// The render the screen has now - padded, when it is.
|
|
32
|
+
const render = screen.render;
|
|
31
33
|
screen.render = function (width) {
|
|
32
34
|
return render.call(this, Math.max(1, width - panelWidth()));
|
|
33
35
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { HStack, Spacer, VStack } from "@earendil-works/pi-tui";
|
|
2
|
+
/**
|
|
3
|
+
* Room around the whole of turri's screen: a row above and below, two columns either side - a cell is about twice as
|
|
4
|
+
* tall as it is wide.
|
|
5
|
+
*
|
|
6
|
+
* pi draws its screen one of two ways, and each is padded as it is drawn: full screen (pi's default), a layout - its
|
|
7
|
+
* root put in the middle of blank rows and columns; the regular screen, the lines its render gives - asked for in fewer
|
|
8
|
+
* columns, indented, with blank lines above and below. Overlays are laid over the whole terminal, so one that should
|
|
9
|
+
* keep to the padding is given it as its margin.
|
|
10
|
+
*/
|
|
11
|
+
export const SCREEN_PADDING = { top: 1, right: 2, bottom: 1, left: 2 };
|
|
12
|
+
/** The columns inside the padding. */
|
|
13
|
+
export function paddedColumns(columns) {
|
|
14
|
+
return Math.max(1, columns - SCREEN_PADDING.left - SCREEN_PADDING.right);
|
|
15
|
+
}
|
|
16
|
+
/** The rows inside the padding. */
|
|
17
|
+
export function paddedRows(rows) {
|
|
18
|
+
return Math.max(1, rows - SCREEN_PADDING.top - SCREEN_PADDING.bottom);
|
|
19
|
+
}
|
|
20
|
+
/** Each padded layout root, to what it pads. */
|
|
21
|
+
const content = new WeakMap();
|
|
22
|
+
/** What a layout root pads - the root itself when it is not padded. */
|
|
23
|
+
export function unpadded(root) {
|
|
24
|
+
return content.get(root) ?? root;
|
|
25
|
+
}
|
|
26
|
+
/** A blank column as wide as the padding on its side: the row gives it what it measures. */
|
|
27
|
+
const blankColumns = (columns) => ({ render: () => [" ".repeat(columns)], invalidate() { } });
|
|
28
|
+
function padded(root) {
|
|
29
|
+
const row = new HStack([
|
|
30
|
+
{ component: blankColumns(SCREEN_PADDING.left), basis: SCREEN_PADDING.left },
|
|
31
|
+
{ component: root, basis: 0, grow: 1 },
|
|
32
|
+
{ component: blankColumns(SCREEN_PADDING.right), basis: SCREEN_PADDING.right },
|
|
33
|
+
]);
|
|
34
|
+
const whole = new VStack([
|
|
35
|
+
{ component: new Spacer(SCREEN_PADDING.top), basis: SCREEN_PADDING.top },
|
|
36
|
+
// As pi lays out its own transcript: the rows the padding leaves, whatever it measures.
|
|
37
|
+
{ component: row, basis: 0, grow: 1, minSize: 1 },
|
|
38
|
+
{ component: new Spacer(SCREEN_PADDING.bottom), basis: SCREEN_PADDING.bottom },
|
|
39
|
+
]);
|
|
40
|
+
content.set(whole, root);
|
|
41
|
+
return whole;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* pi's screen drawn inside the padding, from its next frame - and every layout root it is given after, so the chat
|
|
45
|
+
* narrowed beside the docked panel (`narrowChat`) is padded too. Once a screen: pi starts a new one when it changes how
|
|
46
|
+
* it draws, which is padded when it is passed in.
|
|
47
|
+
*/
|
|
48
|
+
export function padScreen(tui) {
|
|
49
|
+
const screen = tui;
|
|
50
|
+
if (screen.turriPadded === true)
|
|
51
|
+
return;
|
|
52
|
+
if (typeof screen.setLayoutRoot === "function") {
|
|
53
|
+
// Full screen before pi has given it its layout: padded at a later frame.
|
|
54
|
+
if (screen.layoutRoot === undefined)
|
|
55
|
+
return;
|
|
56
|
+
screen.turriPadded = true;
|
|
57
|
+
const set = screen.setLayoutRoot;
|
|
58
|
+
screen.setLayoutRoot = function (root) {
|
|
59
|
+
set.call(this, root === undefined ? undefined : padded(unpadded(root)));
|
|
60
|
+
};
|
|
61
|
+
screen.setLayoutRoot(screen.layoutRoot);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
screen.turriPadded = true;
|
|
65
|
+
// Read from the prototype: the screen's own render, not one laid over it.
|
|
66
|
+
const render = Object.getPrototypeOf(tui).render;
|
|
67
|
+
const left = " ".repeat(SCREEN_PADDING.left);
|
|
68
|
+
screen.render = function (width) {
|
|
69
|
+
const lines = render.call(this, paddedColumns(width)).map((line) => `${left}${line}`);
|
|
70
|
+
return [...new Array(SCREEN_PADDING.top).fill(""), ...lines, ...new Array(SCREEN_PADDING.bottom).fill("")];
|
|
71
|
+
};
|
|
72
|
+
}
|
package/docs/development.md
CHANGED
|
@@ -54,6 +54,7 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
|
|
|
54
54
|
| `src/regenerate/engine.ts` | choosing and resolving render models |
|
|
55
55
|
| `src/regenerate/job-panel.ts`, `src/regenerate/transcript-view.ts`, `src/regenerate/diff-view.ts` | the job panel, the side panel's colours, and a spec diff as they show it - highlighted and banded |
|
|
56
56
|
| `src/regenerate/side-panel.ts`, `src/regenerate/panel-dock.ts` | the side panel, and the chat narrowed beside it when it is docked |
|
|
57
|
+
| `src/screen-padding.ts` | the blank row above and below the whole screen and the two columns either side, in both of pi's ways of drawing it |
|
|
57
58
|
| `src/regenerate/spec-map.ts`, `src/regenerate/spec-map-view.ts` | regen: each item's mark, from the records, the `.plain` files and the work loop's plan (`mapPlan`), and the map drawn |
|
|
58
59
|
| `src/regenerate/debug-log.ts` | each render agent's whole conversation, always streamed to `.turri/debug/` |
|
|
59
60
|
| `genome/` | what turri knows and can do: `genome/skills/`, the skill library, and `genome/rules/`, the \*\*\*plain authoring rules |
|
|
Binary file
|
package/docs/using.md
CHANGED
|
@@ -58,7 +58,7 @@ A render takes minutes, so in the TUI every render tool starts the work as a pul
|
|
|
58
58
|
- **The panel** above the input, while something runs, shows on top what finished - a line a module, its pulses oldest first, each with its mark (green, red or amber, as in the side panel), number and kind - what it regenerated is in the side panel - `cognio … 2 earlier · ✓ 3 regenerate · ✓ 4 conformance`, the newest that fit the width - then a rule, and under it each running pulse - `cognio · regenerate pulse 5`, `cognio · conformance pulse 6` - its time and cost, and what its agents are doing. A module whose work is being planned into pulses shows at once, before any pulse runs - `shorty · planning the pulses · 12s` - the planner is an agent, and takes a while; a `/regenerate` says it began the moment it is sent. Pulses are numbered within their module, as regen numbers them. `ctrl+o` expands the agents' trace alone; what finished stays a line a module. The line of keys under it - `ctrl+o to expand the trace · ctrl+h to peek at regen` - has each key breathing, fully there to a little faint and back, until it is first pressed: once `ctrl+o` has opened the trace, or the side panel has been opened, that key is still for the rest of the session and the other breathes on. What waits is not in it, and it shows no spec diff - the side panel has both. With nothing running it is gone.
|
|
59
59
|
- **A pulse's change** is what its agent works from, not a text diff of the module's file: the items `<spec change>` gives the agent - only what the pulse's functional specs use, an import's items as well as the module's own, a code pulse's without the test reqs - each in the file it is written in, under its section, in the file's order (`change-view.ts`).
|
|
60
60
|
- **A spec diff**, in the side panel, reads as an editor shows a diff: its \*\*\*plain highlighted as [plyn](https://github.com/plainlang/plyn) highlights it - section headers, concepts, names in backticks, text in quotes, links, the front matter - each added line filled green and each removed one red, the text on top, the fill the width of the panel ([colours](theming.md#spec-diffs)). It is shown file by file, each file's lines together under its name, with what it adds and takes out - the module being rendered first, the rest by name, and none the diff does not change. Each line has its number in that file: a spec diff is of the spec as read, with what it imports laid in, so a line is placed in the file it is written in - its links read as the parser renders them - and a removed line, in no file now, goes with the change it is part of and has no number. A line is never put in a file on a guess: one that cannot be placed is listed last, under "not found in the spec's files". Where lines of a file are not in the diff, `↕ 28 lines` says how many, as GitHub does - `↑` above the first - and names the section the lines under it are in when it is another than the one above, `↕ 28 lines · ***functional specs***`, as GitHub names a hunk's function: each section is named once.
|
|
61
|
-
- **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 100 columns has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
|
|
61
|
+
- **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for `enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time - or, `s`, sits beside the chat: the chat on the left, the panel in the right half of the screen, and `s` again over the whole screen. It opens as it was last left, for the conversation; a terminal under 104 columns - 100 inside the screen's padding, a blank row above and below everything and two columns either side - has no room for both and shows the panel over the whole screen. It opens on **regen**: the spec itself, in full, with how much of it the code has been regenerated from drawn onto it.
|
|
62
62
|
|
|
63
63
|
Every item of the spec - a definition, a req, a functional spec with its sub-bullets and acceptance tests, what turri plans and diffs by - is a row in the file's order, its \*\*\*plain highlighted as plyn highlights it (as a spec diff is) and wrapped to the width, never cut short. The map has two columns, each headed on the line over it with a line of space under it: **status**, a mark a row - its rung on a ladder every item climbs - and the module's file, its spec. The top of the panel is where it is seen first: the top line, the module tabs, and the keys - each view's, `d talk it through in the chat` first when the module needs you - wrapped, never cut short, a line of space between the top line and the keys; then straight under them a rule, a line of space, and the columns' headings. The pulses view has its keys at the top too, and a line of space under its rule. The legend is a box of its own in the upper right corner, over what is under it, a line a mark, up the ladder. It names every rung, whatever the page shows - to take out only where something is - and a panel under 60 columns wide has none.
|
|
64
64
|
|