@cueloop/client 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +6 -5
- package/src/App.autoclose.test.tsx +2 -2
- package/src/App.diff.test.tsx +71 -39
- package/src/App.plan-review.test.tsx +20 -21
- package/src/App.readonly.test.tsx +7 -3
- package/src/App.test.tsx +73 -14
- package/src/App.tsx +375 -129
- package/src/App.walk.test.tsx +14 -15
- package/src/App.workbench.test.tsx +390 -0
- package/src/__snapshots__/view-plan.regression.test.ts.snap +1 -1
- package/src/annotation-palette.ts +61 -0
- package/src/app-screens.tsx +176 -45
- package/src/app-view-model.ts +7 -65
- package/src/components/AnnotationCard.tsx +1 -1
- package/src/components/AnnotationCards.stories.tsx +87 -0
- package/src/components/AnnotationCards.tsx +303 -0
- package/src/components/AppHeader.stories.tsx +50 -0
- package/src/components/AppHeader.tsx +83 -0
- package/src/components/AppShell.stories.tsx +47 -42
- package/src/components/AppShell.tsx +212 -100
- package/src/components/AppShell.zoom.test.tsx +94 -0
- package/src/components/ChangesColumn.tsx +49 -26
- package/src/components/DiffSheet.scroll.test.tsx +38 -55
- package/src/components/DiffSheet.split.test.tsx +140 -0
- package/src/components/DiffSheet.stories.tsx +55 -53
- package/src/components/DiffSheet.syntax.test.tsx +24 -7
- package/src/components/DiffSheet.tsx +607 -276
- package/src/components/DiscussionMarkerRail.stories.tsx +54 -0
- package/src/components/DiscussionMarkerRail.tsx +181 -0
- package/src/components/EditorGrid.stories.tsx +59 -0
- package/src/components/EditorGrid.tabs.test.tsx +105 -0
- package/src/components/EditorGrid.tsx +338 -0
- package/src/components/EditorGrid.zoom.test.tsx +68 -0
- package/src/components/FileContentsView.stories.tsx +16 -0
- package/src/components/FileContentsView.test.tsx +29 -0
- package/src/components/FileContentsView.tsx +78 -0
- package/src/components/InboxList.tsx +159 -30
- package/src/components/PaneGrid.stories.tsx +84 -0
- package/src/components/PaneGrid.tsx +128 -0
- package/src/components/PanelColumn.stories.tsx +25 -0
- package/src/components/PanelColumn.tsx +95 -0
- package/src/components/ProjectTreeView.stories.tsx +25 -0
- package/src/components/ProjectTreeView.test.tsx +32 -0
- package/src/components/ProjectTreeView.tsx +82 -0
- package/src/components/PromptDialog.tsx +8 -1
- package/src/components/RootOverlay.stories.tsx +44 -0
- package/src/components/RootOverlay.tsx +35 -0
- package/src/components/SettingsDialog.tsx +2 -1
- package/src/components/SurfaceScrollbar.stories.tsx +34 -0
- package/src/components/SurfaceScrollbar.test.tsx +111 -0
- package/src/components/SurfaceScrollbar.tsx +93 -0
- package/src/components/ThreadFooter.stories.tsx +2 -2
- package/src/components/ThreadFooter.test.tsx +31 -0
- package/src/components/ThreadFooter.tsx +12 -7
- package/src/components/ThreadView.tsx +62 -1440
- package/src/components/ThreadsSidebar.tsx +14 -1
- package/src/components/Tooltip.stories.tsx +26 -0
- package/src/components/Tooltip.tsx +72 -0
- package/src/components/WelcomeSurface.stories.tsx +12 -0
- package/src/components/WelcomeSurface.tsx +52 -0
- package/src/components/__snapshots__/stories.test.tsx.snap +509 -559
- package/src/components/diff-sheet-layout.test.ts +125 -55
- package/src/components/diff-sheet-layout.ts +3 -103
- package/src/components/editor-grid.test.ts +86 -0
- package/src/components/editor-grid.ts +138 -0
- package/src/components/fade-title.test.ts +49 -0
- package/src/components/fade-title.ts +61 -0
- package/src/components/file-tree.test.ts +15 -0
- package/src/components/file-tree.ts +59 -4
- package/src/components/pages/Workspace.stories.tsx +2 -2
- package/src/components/primitives/IconButton.tsx +21 -2
- package/src/components/primitives/Tree.tsx +13 -4
- package/src/components/primitives/icons.ts +27 -4
- package/src/components/session-tree.test.ts +20 -0
- package/src/components/session-tree.ts +16 -2
- package/src/components/stories-app.tsx +2 -2
- package/src/config.test.ts +20 -49
- package/src/config.ts +26 -28
- package/src/diff-fold.test.ts +78 -0
- package/src/diff-fold.ts +126 -0
- package/src/diff-syntax.test.ts +16 -0
- package/src/diff-syntax.ts +6 -3
- package/src/discussions.ts +79 -0
- package/src/ghostty-terminal.test.ts +2 -1
- package/src/ghostty-terminal.ts +27 -4
- package/src/intent-dispatch.test.ts +31 -11
- package/src/intent-dispatch.ts +93 -48
- package/src/key-bindings.test.ts +1 -1
- package/src/keymap.test.ts +3 -25
- package/src/keymap.ts +17 -18
- package/src/mark-runs.ts +120 -0
- package/src/pty.ts +10 -2
- package/src/run.ts +17 -7
- package/src/serve.test.ts +4 -8
- package/src/session-controller.curate.test.ts +75 -0
- package/src/session-controller.share.test.ts +1 -0
- package/src/session-controller.tree.test.ts +26 -0
- package/src/session-controller.ts +153 -29
- package/src/slash-palette.ts +83 -0
- package/src/split-diff.test.ts +110 -0
- package/src/split-diff.ts +149 -0
- package/src/test-support.test.ts +15 -3
- package/src/test-support.ts +66 -15
- package/src/thread-chords.test.ts +25 -9
- package/src/thread-chords.ts +33 -8
- package/src/use-annotation-surface.tsx +837 -0
- package/src/use-changes-workbench.ts +151 -0
- package/src/use-frame-measure.ts +38 -0
- package/src/use-settings-dialog.tsx +14 -24
- package/src/use-terminal-virtualizer.ts +233 -0
- package/src/view-diff.ts +114 -0
- package/src/components/ReviewPanel.stories.tsx +0 -100
- package/src/components/ReviewPanel.tsx +0 -103
- package/src/components/ReviewRail.stories.tsx +0 -232
- package/src/components/ReviewRail.tsx +0 -327
- package/src/review-panel.test.ts +0 -113
- package/src/review-panel.ts +0 -83
package/src/test-support.test.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
frameRow,
|
|
15
15
|
isolateUserConfig,
|
|
16
16
|
locateText,
|
|
17
|
+
locateTextInFrame,
|
|
17
18
|
pressKey,
|
|
18
19
|
waitForText,
|
|
19
20
|
type FrameLocation,
|
|
@@ -25,7 +26,7 @@ describe("isolateUserConfig", () => {
|
|
|
25
26
|
const home = mkdtempSync(join(tmpdir(), "cueloop-isolate-"));
|
|
26
27
|
const userConfigPath = join(home, "config.toml");
|
|
27
28
|
|
|
28
|
-
writeFileSync(userConfigPath,
|
|
29
|
+
writeFileSync(userConfigPath, "[ui]\nauto_close = 5\n");
|
|
29
30
|
const priorEnv = process.env.CUELOOP_CONFIG;
|
|
30
31
|
|
|
31
32
|
process.env.CUELOOP_CONFIG = userConfigPath;
|
|
@@ -39,9 +40,9 @@ describe("isolateUserConfig", () => {
|
|
|
39
40
|
const restored = loadConfig({ repoRoot: home });
|
|
40
41
|
|
|
41
42
|
// Assert
|
|
42
|
-
expect(isolated.ui.
|
|
43
|
+
expect(isolated.ui.autoClose).toBe("off");
|
|
43
44
|
expect(process.env.CUELOOP_CONFIG).toBe(userConfigPath);
|
|
44
|
-
expect(restored.ui.
|
|
45
|
+
expect(restored.ui.autoClose).toBe(5);
|
|
45
46
|
} finally {
|
|
46
47
|
if (priorEnv === undefined) delete process.env.CUELOOP_CONFIG;
|
|
47
48
|
else process.env.CUELOOP_CONFIG = priorEnv;
|
|
@@ -250,3 +251,14 @@ describe("dragText", () => {
|
|
|
250
251
|
expect(log.mouseEvents).toContainEqual({ type: "up", x: 9, y: 3 });
|
|
251
252
|
});
|
|
252
253
|
});
|
|
254
|
+
|
|
255
|
+
describe("locateTextInFrame", () => {
|
|
256
|
+
test("finds the 0-based row and column of the first match, null when absent", () => {
|
|
257
|
+
// Arrange
|
|
258
|
+
const frame = "header\n ● draft here\nfooter";
|
|
259
|
+
|
|
260
|
+
// Act + Assert
|
|
261
|
+
expect(locateTextInFrame(frame, "draft")).toEqual({ row: 1, column: 4 });
|
|
262
|
+
expect(locateTextInFrame(frame, "missing")).toBeNull();
|
|
263
|
+
});
|
|
264
|
+
});
|
package/src/test-support.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared driving helpers for the virtual-terminal App suites:
|
|
3
|
-
*
|
|
4
|
-
* render pass,
|
|
2
|
+
* Shared driving helpers for the virtual-terminal App suites: an input settles
|
|
3
|
+
* when its painted frame stops changing across consecutive event-loop turns
|
|
4
|
+
* (macrotask yield, render pass, visual-idle wait per turn) - no fixed-duration
|
|
5
5
|
* sleeps. Waits on daemon round-trips go through waitForFrame/waitFor with
|
|
6
6
|
* generous pass budgets.
|
|
7
7
|
*/
|
|
@@ -12,6 +12,16 @@ import type { TestRendererSetup } from "@opentui/core/testing";
|
|
|
12
12
|
/** Pass budget for waits that include daemon or subprocess round-trips. */
|
|
13
13
|
export const WAIT_PASSES = { maxPasses: 400 };
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The CUELOOP_CONFIG path that isolates a test from the developer's real user
|
|
17
|
+
* config: a file inside the test home that does not exist unless the test
|
|
18
|
+
* writes it. Subprocess harnesses set this in the child env; in-process suites
|
|
19
|
+
* use `isolateUserConfig`.
|
|
20
|
+
*/
|
|
21
|
+
export function isolatedUserConfigPath(home: string, fileName = "no-config.toml"): string {
|
|
22
|
+
return join(home, fileName);
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* Isolate the user config: point CUELOOP_CONFIG into the test home so
|
|
17
27
|
* loadConfig never reads the developer's real ~/.config/cueloop (a persisted
|
|
@@ -20,10 +30,10 @@ export const WAIT_PASSES = { maxPasses: 400 };
|
|
|
20
30
|
* defaults; pass a file name for suites that write and assert a config of
|
|
21
31
|
* their own. Call in beforeEach; invoke the returned restore in afterEach.
|
|
22
32
|
*/
|
|
23
|
-
export function isolateUserConfig(home: string, fileName
|
|
33
|
+
export function isolateUserConfig(home: string, fileName?: string): () => void {
|
|
24
34
|
const priorUserConfig = process.env.CUELOOP_CONFIG;
|
|
25
35
|
|
|
26
|
-
process.env.CUELOOP_CONFIG =
|
|
36
|
+
process.env.CUELOOP_CONFIG = isolatedUserConfigPath(home, fileName);
|
|
27
37
|
|
|
28
38
|
return () => {
|
|
29
39
|
if (priorUserConfig === undefined) delete process.env.CUELOOP_CONFIG;
|
|
@@ -40,15 +50,44 @@ export function allowEventLoopUpdates(): void {
|
|
|
40
50
|
globalThis.IS_REACT_ACT_ENVIRONMENT = false;
|
|
41
51
|
}
|
|
42
52
|
|
|
53
|
+
/** Turns of the event loop a frame must survive unchanged before an input counts as settled. */
|
|
54
|
+
const SETTLE_QUIET_TURNS = 2;
|
|
55
|
+
|
|
56
|
+
/** Upper bound on settle turns, so a surface that keeps changing never stalls a test. */
|
|
57
|
+
const SETTLE_MAX_TURNS = 40;
|
|
58
|
+
|
|
59
|
+
/** The painted frame with its colors, so a caret cell (background only) counts as a change. */
|
|
60
|
+
function paintedFrame(setup: TestRendererSetup): string {
|
|
61
|
+
return JSON.stringify(
|
|
62
|
+
setup
|
|
63
|
+
.captureSpans()
|
|
64
|
+
.lines.map((line) =>
|
|
65
|
+
line.spans.map((span) => [span.text, span.fg?.toInts(), span.bg?.toInts()]),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
71
|
+
* Wait for an input to land: React commits its state on its own scheduler, which the renderer's
|
|
72
|
+
* idle check cannot see, so on a slow machine a single idle wait returns between the key and its
|
|
73
|
+
* paint. Instead, run turns of the loop until the painted frame is unchanged across
|
|
74
|
+
* `SETTLE_QUIET_TURNS` consecutive turns.
|
|
46
75
|
*/
|
|
47
76
|
export async function settle(setup: TestRendererSetup): Promise<void> {
|
|
48
77
|
allowEventLoopUpdates();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
let previous = "";
|
|
79
|
+
let quietTurns = 0;
|
|
80
|
+
|
|
81
|
+
for (let turn = 0; turn < SETTLE_MAX_TURNS; turn++) {
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
83
|
+
await setup.renderOnce();
|
|
84
|
+
await setup.waitForVisualIdle();
|
|
85
|
+
const frame = paintedFrame(setup);
|
|
86
|
+
|
|
87
|
+
quietTurns = frame === previous ? quietTurns + 1 : 0;
|
|
88
|
+
if (quietTurns >= SETTLE_QUIET_TURNS) return;
|
|
89
|
+
previous = frame;
|
|
90
|
+
}
|
|
52
91
|
}
|
|
53
92
|
|
|
54
93
|
/** Drive one key press: letters type as text; named keys use KeyCodes ids. */
|
|
@@ -58,6 +97,8 @@ export async function press(setup: TestRendererSetup, key: string): Promise<void
|
|
|
58
97
|
else if (key === "backspace") setup.mockInput.pressKey("BACKSPACE");
|
|
59
98
|
else if (key === "left") setup.mockInput.pressKey("ARROW_LEFT");
|
|
60
99
|
else if (key === "right") setup.mockInput.pressKey("ARROW_RIGHT");
|
|
100
|
+
else if (key === "up") setup.mockInput.pressKey("ARROW_UP");
|
|
101
|
+
else if (key === "down") setup.mockInput.pressKey("ARROW_DOWN");
|
|
61
102
|
else return typeText(setup, key);
|
|
62
103
|
await settle(setup);
|
|
63
104
|
}
|
|
@@ -83,17 +124,27 @@ export interface FrameLocation {
|
|
|
83
124
|
column: number;
|
|
84
125
|
}
|
|
85
126
|
|
|
86
|
-
/** Locate the first
|
|
87
|
-
export function
|
|
88
|
-
const frame = setup.captureCharFrame();
|
|
89
|
-
|
|
127
|
+
/** Locate the first line of a text frame containing the needle; null when absent. */
|
|
128
|
+
export function locateTextInFrame(frame: string, needle: string): FrameLocation | null {
|
|
90
129
|
for (const [row, line] of frame.split("\n").entries()) {
|
|
91
130
|
const column = line.indexOf(needle);
|
|
92
131
|
|
|
93
132
|
if (column !== -1) return { row, column };
|
|
94
133
|
}
|
|
95
134
|
|
|
96
|
-
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Locate the first visual line containing the needle: 0-based row and start column. */
|
|
139
|
+
export function locateText(setup: TestRendererSetup, needle: string): FrameLocation {
|
|
140
|
+
const frame = setup.captureCharFrame();
|
|
141
|
+
const location = locateTextInFrame(frame, needle);
|
|
142
|
+
|
|
143
|
+
if (location === null) {
|
|
144
|
+
throw new Error(`locateText ${JSON.stringify(needle)} not found.\nframe:\n${frame}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return location;
|
|
97
148
|
}
|
|
98
149
|
|
|
99
150
|
/** The 0-based visual row of the needle - for relative vertical-layout assertions. */
|
|
@@ -21,8 +21,31 @@ describe("resolveThreadChord", () => {
|
|
|
21
21
|
// Assert
|
|
22
22
|
expect(resolveThreadChord({ name: "e", ctrl: true }, owner)).toEqual({ type: "edit" });
|
|
23
23
|
expect(resolveThreadChord({ name: "s", ctrl: true }, owner)).toEqual({ type: "share" });
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("in a diff, option chords act on the caret's row and file; the rest still reach the rail", () => {
|
|
27
|
+
// Arrange
|
|
28
|
+
const diff = { ...owner, isDiff: true };
|
|
29
|
+
|
|
30
|
+
// Assert - the code-row primitives
|
|
31
|
+
expect(resolveThreadChord({ name: "x", meta: true }, diff)).toEqual({ type: "rejectChange" });
|
|
32
|
+
expect(resolveThreadChord({ name: "X", meta: true }, diff)).toEqual({ type: "rejectHunk" });
|
|
33
|
+
expect(resolveThreadChord({ name: "u", meta: true }, diff)).toEqual({
|
|
34
|
+
type: "restoreCuration",
|
|
35
|
+
});
|
|
36
|
+
expect(resolveThreadChord({ name: "c", meta: true }, diff)).toEqual({ type: "foldFile" });
|
|
37
|
+
expect(resolveThreadChord({ name: "d", meta: true }, diff)).toEqual({ type: "toggleDiffView" });
|
|
38
|
+
expect(resolveThreadChord({ name: "k", meta: true }, diff)).toEqual({ type: "walkStart" });
|
|
39
|
+
// the rail letters are untouched
|
|
40
|
+
expect(resolveThreadChord({ name: "n", meta: true }, diff)).toEqual({ type: "nextAnnotation" });
|
|
41
|
+
// rejecting is the owner's, and closed once resolved
|
|
42
|
+
expect(resolveThreadChord({ name: "x", meta: true }, { ...diff, isOwner: false })).toEqual({
|
|
43
|
+
type: "status",
|
|
44
|
+
message: "observer - read-only",
|
|
45
|
+
});
|
|
46
|
+
expect(resolveThreadChord({ name: "k", meta: true }, { ...diff, resolved: true })).toEqual({
|
|
47
|
+
type: "status",
|
|
48
|
+
message: "review submitted - read-only",
|
|
26
49
|
});
|
|
27
50
|
});
|
|
28
51
|
|
|
@@ -43,10 +66,6 @@ describe("resolveThreadChord", () => {
|
|
|
43
66
|
expect(resolveThreadChord({ name: "u", meta: true }, owner)).toEqual({
|
|
44
67
|
type: "restoreCuration",
|
|
45
68
|
});
|
|
46
|
-
expect(resolveThreadChord({ name: "w", meta: true }, owner)).toEqual({
|
|
47
|
-
type: "resizeReviewPanel",
|
|
48
|
-
direction: 1,
|
|
49
|
-
});
|
|
50
69
|
expect(resolveThreadChord({ name: "x", meta: true }, { ...owner, isOwner: false })).toEqual({
|
|
51
70
|
type: "status",
|
|
52
71
|
message: "observer - read-only",
|
|
@@ -76,9 +95,6 @@ describe("resolveThreadChord", () => {
|
|
|
76
95
|
expect(resolveThreadChord({ name: "return", ctrl: true }, collaborator)).toEqual(readOnly);
|
|
77
96
|
expect(resolveThreadChord({ name: "e", ctrl: true }, collaborator)).toEqual(readOnly);
|
|
78
97
|
expect(resolveThreadChord({ name: "s", ctrl: true }, collaborator)).toEqual(readOnly);
|
|
79
|
-
expect(resolveThreadChord({ name: "r", ctrl: true }, collaborator)).toEqual({
|
|
80
|
-
type: "cycleReviewPanel",
|
|
81
|
-
});
|
|
82
98
|
expect(
|
|
83
99
|
resolveThreadChord({ name: "return", ctrl: true }, { ...owner, resolved: true }),
|
|
84
100
|
).toBeNull();
|
package/src/thread-chords.ts
CHANGED
|
@@ -24,8 +24,19 @@ export interface ThreadChordContext {
|
|
|
24
24
|
resolved: boolean;
|
|
25
25
|
/** The rail shows the tree: next / previous move its selection instead of the cards. */
|
|
26
26
|
treeActive: boolean;
|
|
27
|
+
/** A diff review: the block chords act on the code row under the caret (reject, fold, walk). */
|
|
28
|
+
isDiff?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
/** The diff-review chords: option plus a letter, acting on the caret's row or file. */
|
|
32
|
+
export const DIFF_CHORD_ENTRIES = [
|
|
33
|
+
{ keys: "⌥x / ⌥X", label: "reject the change / the hunk" },
|
|
34
|
+
{ keys: "⌥u", label: "restore the last rejection" },
|
|
35
|
+
{ keys: "⌥c", label: "collapse the file to its band" },
|
|
36
|
+
{ keys: "⌥d", label: "unified / split (when zoomed)" },
|
|
37
|
+
{ keys: "⌥k", label: "start the guided walk" },
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
29
40
|
/** The cheatsheet rows for the chords, in the same order they are resolved. */
|
|
30
41
|
export const THREAD_CHORD_ENTRIES = [
|
|
31
42
|
{ keys: "⌃enter", label: "submit the review" },
|
|
@@ -70,11 +81,32 @@ export function resolveThreadChord(
|
|
|
70
81
|
return context.resolved ? null : { type: "openSubmit" };
|
|
71
82
|
}
|
|
72
83
|
if (key.ctrl) return resolveSessionChord(key.name, context);
|
|
73
|
-
if (key.meta)
|
|
84
|
+
if (key.meta)
|
|
85
|
+
return (
|
|
86
|
+
(context.isDiff && resolveDiffChord(key.name, context)) || resolveRailChord(key.name, context)
|
|
87
|
+
);
|
|
74
88
|
|
|
75
89
|
return null;
|
|
76
90
|
}
|
|
77
91
|
|
|
92
|
+
/** The diff's row and file chords; null lets the rail chords answer the letter. */
|
|
93
|
+
function resolveDiffChord(name: string, context: ThreadChordContext): Intent | null {
|
|
94
|
+
switch (name) {
|
|
95
|
+
case "x":
|
|
96
|
+
return mutating({ type: "rejectChange" }, context);
|
|
97
|
+
case "X":
|
|
98
|
+
return mutating({ type: "rejectHunk" }, context);
|
|
99
|
+
case "c":
|
|
100
|
+
return { type: "foldFile" };
|
|
101
|
+
case "d":
|
|
102
|
+
return { type: "toggleDiffView" };
|
|
103
|
+
case "k":
|
|
104
|
+
return context.resolved ? RESOLVED : { type: "walkStart" };
|
|
105
|
+
default:
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
78
110
|
/** The answers a blocked primitive gets - the same words the keymap uses. */
|
|
79
111
|
const READ_ONLY: Intent = { type: "status", message: "observer - read-only" };
|
|
80
112
|
const RESOLVED: Intent = { type: "status", message: "review submitted - read-only" };
|
|
@@ -93,8 +125,6 @@ function resolveSessionChord(name: string, context: ThreadChordContext): Intent
|
|
|
93
125
|
return mutating({ type: "edit" }, context);
|
|
94
126
|
case "s":
|
|
95
127
|
return context.isOwner ? { type: "share" } : READ_ONLY;
|
|
96
|
-
case "r":
|
|
97
|
-
return { type: "cycleReviewPanel" };
|
|
98
128
|
default:
|
|
99
129
|
return null;
|
|
100
130
|
}
|
|
@@ -133,11 +163,6 @@ function resolveRailChord(name: string, context: ThreadChordContext): Intent | n
|
|
|
133
163
|
return mutating({ type: "cut" }, context);
|
|
134
164
|
case "u":
|
|
135
165
|
return mutating({ type: "restoreCuration" }, context);
|
|
136
|
-
// letters only: escape-prefixed punctuation reads as control sequences, not keys
|
|
137
|
-
case "w":
|
|
138
|
-
return { type: "resizeReviewPanel", direction: 1 };
|
|
139
|
-
case "s":
|
|
140
|
-
return { type: "resizeReviewPanel", direction: -1 };
|
|
141
166
|
default:
|
|
142
167
|
return null;
|
|
143
168
|
}
|