@cueloop/client 0.1.0-alpha.90 → 0.1.0-alpha.91

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": "@cueloop/client",
3
- "version": "0.1.0-alpha.90",
3
+ "version": "0.1.0-alpha.91",
4
4
  "description": "The cueloop review UI: projection renderer, keyboard grammar, review rail",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -27,10 +27,10 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@cueloop/daemon": "0.1.0-alpha.90",
31
- "@cueloop/extension-api": "0.1.0-alpha.90",
32
- "@cueloop/integration-obsidian": "0.1.0-alpha.90",
33
- "@cueloop/schema": "0.1.0-alpha.90",
30
+ "@cueloop/daemon": "0.1.0-alpha.91",
31
+ "@cueloop/extension-api": "0.1.0-alpha.91",
32
+ "@cueloop/integration-obsidian": "0.1.0-alpha.91",
33
+ "@cueloop/schema": "0.1.0-alpha.91",
34
34
  "@opentui/core": "^0.5.11",
35
35
  "@opentui/keymap": "0.5.11",
36
36
  "@opentui/react": "^0.5.11",
package/src/App.tsx CHANGED
@@ -413,10 +413,14 @@ function initialThreadsOpen(
413
413
  return layout ? layout.threads : sessionId === undefined;
414
414
  }
415
415
 
416
- function initialFocusedPane(sessionId: string | undefined, zoomed: boolean): FocusPane {
416
+ function initialFocusedPane(
417
+ sessionId: string | undefined,
418
+ zoomed: boolean,
419
+ layout?: LaunchLayout,
420
+ ): FocusPane {
417
421
  if (sessionId === undefined) return "threads";
418
422
 
419
- return zoomed ? "changes" : "thread";
423
+ return layout?.focusPane ?? (zoomed ? "changes" : "thread");
420
424
  }
421
425
 
422
426
  function bareShellDefersKeys(
@@ -609,7 +613,7 @@ export function App({
609
613
  // the Changes + Project right region and its editor grid (tabs, splits, zoom)
610
614
  const workbench = useChangesWorkbench({ layout });
611
615
  const [focusedPane, setFocusedPane] = useState<FocusPane>(() =>
612
- initialFocusedPane(sessionId, workbench.zoomed),
616
+ initialFocusedPane(sessionId, workbench.zoomed, layout),
613
617
  );
614
618
  // only the session view persists from here; the bare shell owns its own (NoThreadShell), so this
615
619
  // workbench stays inactive with no session and never clobbers what the shell saved
@@ -16,7 +16,7 @@ import { DaemonServer } from "@cueloop/daemon";
16
16
  import type { Thread } from "@cueloop/schema";
17
17
  import { App } from "./App";
18
18
  import { loadConfig } from "./config";
19
- import { planLayout, reviewLayout } from "./launch-layout";
19
+ import { pairLayout, planLayout, reviewLayout } from "./launch-layout";
20
20
  import {
21
21
  dragText,
22
22
  isolateUserConfig,
@@ -113,6 +113,19 @@ const rightToggleColumn = (setup: Setup): number => toggleColumn(setup, NERD.sid
113
113
  const railToggleColumn = (setup: Setup): number => toggleColumn(setup, NERD.sidebarRightOff);
114
114
 
115
115
  describe("the four-pane workbench", () => {
116
+ test("pair starts focused on the Project tree beside the zoomed Changes editor", async () => {
117
+ const setup = await renderReadyApp(
118
+ <App home={home} sessionId={session.id} layout={pairLayout()} />,
119
+ { width: 160, height: 20 },
120
+ );
121
+
122
+ await waitForText(setup, "README.md");
123
+ expect(setup.captureCharFrame()).toContain("Changes");
124
+ await pressKey(setup, "RETURN");
125
+ await waitForText(setup, "Workbench Fixture");
126
+ setup.renderer.destroy();
127
+ });
128
+
116
129
  test("a live thread's Changes tree follows edits and a clean working tree", async () => {
117
130
  const liveSession = server.core.sessionCreate({
118
131
  workspace: { repoRoot: repo, branch: "main" },
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { runClient, type RunClientOptions } from "./run";
2
2
  export { snapshotWorkbench, type RepoDiff } from "./workbench-snapshot";
3
3
  export {
4
4
  defaultLayout,
5
+ pairLayout,
5
6
  planLayout,
6
7
  pullRequestReviewLayout,
7
8
  reviewLayout,
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import {
3
3
  defaultLayout,
4
4
  layoutFromPanes,
5
+ pairLayout,
5
6
  planLayout,
6
7
  pullRequestReviewLayout,
7
8
  reviewLayout,
@@ -13,6 +14,16 @@ describe("launch layout factories", () => {
13
14
  expect(reviewLayout()).toEqual({ threads: true, rightSidebar: "changes", zoomChanges: true });
14
15
  });
15
16
 
17
+ test("pair opens the Project tree focused beside a zoomed Changes editor", () => {
18
+ expect(pairLayout()).toEqual({
19
+ threads: true,
20
+ rightSidebar: "changes",
21
+ zoomChanges: true,
22
+ projectMode: "tree",
23
+ focusPane: "project",
24
+ });
25
+ });
26
+
16
27
  test("pull request review keeps its brief beside the Changes panel", () => {
17
28
  // Assert
18
29
  expect(pullRequestReviewLayout()).toEqual({
@@ -12,6 +12,10 @@ export interface LaunchLayout {
12
12
  rightSidebar: "changes" | "project" | "off";
13
13
  /** The Changes editor is zoomed - the Thread pane hides so the diff fills the middle. */
14
14
  zoomChanges: boolean;
15
+ /** Which Project sidebar tab opens while the Changes editor stays visible. */
16
+ projectMode?: "changes" | "tree";
17
+ /** Keyboard focus when opening a selected Thread. */
18
+ focusPane?: "threads" | "thread" | "changes" | "project";
15
19
  }
16
20
 
17
21
  /** `cueloop diff`: the diff front and center. */
@@ -19,6 +23,17 @@ export function reviewLayout(): LaunchLayout {
19
23
  return { threads: true, rightSidebar: "changes", zoomChanges: true };
20
24
  }
21
25
 
26
+ /** `cueloop pair`: browse project files beside the zoomed Changes editor. */
27
+ export function pairLayout(): LaunchLayout {
28
+ return {
29
+ threads: true,
30
+ rightSidebar: "changes",
31
+ zoomChanges: true,
32
+ projectMode: "tree",
33
+ focusPane: "project",
34
+ };
35
+ }
36
+
22
37
  /** `cueloop review <pr>`: PR context beside the Changes panel. */
23
38
  export function pullRequestReviewLayout(): LaunchLayout {
24
39
  return { threads: true, rightSidebar: "changes", zoomChanges: false };
@@ -44,7 +44,7 @@ function initialPanes(options?: ChangesWorkbenchOptions): InitialPanes {
44
44
  return {
45
45
  projectOpen: layout.rightSidebar !== "off",
46
46
  changesOpen,
47
- projectMode: layout.rightSidebar === "project" ? "tree" : "changes",
47
+ projectMode: layout.projectMode ?? (layout.rightSidebar === "project" ? "tree" : "changes"),
48
48
  // zoom fills the middle with the Changes editor, so a hand-edited layout that zooms without one
49
49
  // would strand a blank center with the Thread pane hidden; only honor zoom when Changes is open
50
50
  zoomed: layout.zoomChanges && changesOpen,
@@ -99,7 +99,7 @@ export function useChangesWorkbench(options?: ChangesWorkbenchOptions): ChangesW
99
99
  seenSession.current = sessionId;
100
100
  setProjectOpen(isDiff);
101
101
  setChangesOpen(isDiff);
102
- setProjectMode(isDiff ? "changes" : "tree");
102
+ setProjectMode(isDiff ? (options?.layout?.projectMode ?? "changes") : "tree");
103
103
  rememberedChanges.current = isDiff;
104
104
  // a non-diff thread has no Changes editor; leaving zoom on would strand the hidden Thread pane
105
105
  if (!isDiff) setZoomed(false);