Skip to content

Commit 081554e

Browse files
committed
Merge branch dev into published
2 parents b2fe3eb + c801b08 commit 081554e

13 files changed

Lines changed: 268 additions & 80 deletions

.github/copilot-instructions.md

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,2 @@
1-
## Design Context
2-
3-
### Users
4-
5-
VS Code developers adopting or practicing Clojure/ClojureScript. The audience spans from **complete Clojure beginners** (the explicit growth mission) to **experienced Clojurians** who chose VS Code as their editor. They arrive with VS Code muscle memory and expectations — syntax highlighting, command palette, familiar keybindings — and need the REPL to feel like a natural extension of that, not a foreign system bolted on.
6-
7-
The context of use is deep-focus programming: long sessions, high cognitive load, frequent context-switching between code and REPL output. The interface must never compete for attention with the code itself.
8-
9-
### Brand Personality
10-
11-
**Approachable, precise, alive.**
12-
13-
Calva is named after Calvados — a spirit that gains its character from what it's distilled from (CIDER/nREPL) and what it matures in (VS Code). The brand voice is that of a master distiller: confident but unhurried, opinionated but welcoming, spartan but *not* poor. As the Tao states: "VS Code and Clojure brought together has the capacity to create something amazingly rich and luxurious."
14-
15-
The emotional goals are **confidence** (I know what's happening), **flow** (nothing breaks my concentration), and **precision** (sharp, exact, professional). The anti-reference is GitLens — invasive, attention-grabbing, too much visual presence in the editor.
16-
17-
### Aesthetic Direction
18-
19-
- **Theme**: Native VS Code. Custom surfaces (webviews, output panels) should feel like they belong to the user's chosen VS Code theme, not to Calva's own visual system. Use `var(--vscode-*)` CSS custom properties as the primary palette.
20-
- **Brand color**: Golden amber `#db9550` — core brand accent. Use sparingly: status indicators, the Calva logo, moments of identity. Never as a dominant surface color.
21-
- **Font**: Fira Code is the bundled code font for webviews. Body/UI text inherits from VS Code's editor font family.
22-
- **Tone**: Quiet competence. The interface should feel like a well-made tool — present when needed, invisible when not. Spartan in the Halloway sense: few features, each done right.
23-
- **Anti-patterns**: Invasive decorations, unsolicited overlays, attention-competing UI, feature clutter. Calva should never make the user aware of Calva when they're trying to think about Clojure.
24-
25-
### Design Principles
26-
27-
1. **The VS Code way is Calva's way.** Leverage existing VS Code patterns and conventions. What's old is old; what's new should be as easy as possible to pick up. Don't invent new interaction patterns when VS Code already has one.
28-
29-
2. **Remove obstacles to the REPL.** Every UI decision should be evaluated by: does this help or hinder the developer's path to evaluating code and understanding results? The REPL connection is the critical moment; evaluation results are the critical data.
30-
31-
3. **Spartan, not poor.** Resist feature creep. Few knobs, sane defaults. But the knobs that exist should feel luxurious — well-considered, well-placed, well-documented. Quality over quantity.
32-
33-
4. **Simplify the complex.** Calva has organic complexity (multiple output destinations, REPL window maintenance, session routing). Design should actively work to reduce cognitive load around these areas rather than expose the underlying complexity.
34-
35-
5. **Invisible when working, present when needed.** Status information, connection state, session routing — these should be discoverable but not intrusive. The developer's attention belongs to their code, not to Calva's UI.
1+
* When delegating Calva design/development to a subagent, use the calva-nucleus agent. It has the Tao of Calva encoded.
2+
* When designing UI and Ux, follow the principles in [.impeccable.md](../.impeccable.md).

.impeccable.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Users
44

5-
VS Code developers adopting or practicing Clojure/ClojureScript. The audience spans from **complete Clojure beginners** (the explicit growth mission) to **experienced Clojurians** who chose VS Code as their editor. They arrive with VS Code muscle memory and expectations — syntax highlighting, command palette, familiar keybindings — and need the REPL to feel like a natural extension of that, not a foreign system bolted on.
5+
VS Code developers adopting or practicing Clojure/ClojureScript and all dialects. The audience spans from **complete Clojure beginners** (the explicit growth mission) to **experienced Clojurians** who chose VS Code as their editor. They arrive with VS Code muscle memory and expectations — syntax highlighting, command palette, familiar keybindings — and need the REPL to feel like a natural extension of that, not a foreign system bolted on.
66

77
The context of use is deep-focus programming: long sessions, high cognitive load, frequent context-switching between code and REPL output. The interface must never compete for attention with the code itself.
88

@@ -12,7 +12,7 @@ The context of use is deep-focus programming: long sessions, high cognitive load
1212

1313
Calva is named after Calvados — a spirit that gains its character from what it's distilled from (CIDER/nREPL) and what it matures in (VS Code). The brand voice is that of a master distiller: confident but unhurried, opinionated but welcoming, spartan but *not* poor. As the Tao states: "VS Code and Clojure brought together has the capacity to create something amazingly rich and luxurious."
1414

15-
The emotional goals are **confidence** (I know what's happening), **flow** (nothing breaks my concentration), and **precision** (sharp, exact, professional). The anti-reference is GitLens — invasive, attention-grabbing, too much visual presence in the editor.
15+
The emotional goals are **confidence** (I know what's happening), **flow** (nothing breaks my concentration), and **precision** (sharp, exact, professional). The anti-reference is invasive, attention-grabbing, too much visual presence in the editor.
1616

1717
## Aesthetic Direction
1818

@@ -30,6 +30,4 @@ The emotional goals are **confidence** (I know what's happening), **flow** (noth
3030

3131
3. **Spartan, not poor.** Resist feature creep. Few knobs, sane defaults. But the knobs that exist should feel luxurious — well-considered, well-placed, well-documented. Quality over quantity.
3232

33-
4. **Simplify the complex.** Calva has organic complexity (multiple output destinations, REPL window maintenance, session routing). Design should actively work to reduce cognitive load around these areas rather than expose the underlying complexity.
34-
35-
5. **Invisible when working, present when needed.** Status information, connection state, session routing — these should be discoverable but not intrusive. The developer's attention belongs to their code, not to Calva's UI.
33+
4. **Invisible when working, present when needed.** Status information, connection state, session routing — these should be discoverable but not intrusive. The developer's attention belongs to their code, not to Calva's UI.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changes to Calva.
44

55
## [Unreleased]
66

7+
## [2.0.584] - 2026-05-07
8+
9+
- [enable new clojure-lsp code actions: inline function, if<->cond, extract selection to function, move to :let](https://github.com/BetterThanTomorrow/calva/issues/3204)
10+
- Fix: [Calva forgets the renamed session name on disconnect->reconnect](https://github.com/BetterThanTomorrow/calva/issues/3207)
11+
712
## [2.0.583] - 2026-05-04
813

914
- [Enable renaming of REPL sessions](https://github.com/BetterThanTomorrow/calva/issues/3197)

package-lock.json

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Calva: Clojure & ClojureScript Interactive Programming",
44
"description": "Integrated REPL, formatter, Paredit, and more. Powered by cider-nrepl and clojure-lsp.",
55
"icon": "assets/calva.png",
6-
"version": "2.0.583",
6+
"version": "2.0.584",
77
"publisher": "betterthantomorrow",
88
"author": {
99
"name": "Better Than Tomorrow",

src/connector.ts

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ async function connectViaWebSocket(
9898
isJackIn = false
9999
): Promise<ConnectResult> {
100100
let activeClient: nrepl.NReplClient | undefined;
101+
let preservedRenames: Partial<sessionRoleUtils.SessionRoleKeys> | undefined;
101102
const baseSessionNames = sessionRoleUtils.deriveSessionRoleKeys(connectSequence);
102103
const projectRootPath = state.getProjectRootUri().fsPath;
103104
const projectRoot = state.getProjectRootUri().toString();
@@ -193,6 +194,7 @@ async function connectViaWebSocket(
193194
connectSequence,
194195
baseSessionNames,
195196
suffix: resolution.suffix,
197+
renamedSessionNames: preservedRenames,
196198
},
197199
});
198200

@@ -211,19 +213,29 @@ async function connectViaWebSocket(
211213
});
212214
clientRegistry.setCljcTargetForConnection(client.clientKey, 'primary');
213215

216+
// Apply preserved rename from previous browser connection
217+
const renamedPrimary = preservedRenames?.primary;
218+
if (renamedPrimary && renamedPrimary !== mainKey) {
219+
sessionRegistry.renameSession(mainKey, renamedPrimary);
220+
}
221+
const effectiveMainKey = renamedPrimary ?? mainKey;
222+
preservedRenames = undefined;
223+
214224
status.update();
215-
output.appendLineOtherOut(`Connected session: ${mainKey}, ws://${wsHost}:${currentPort}`);
225+
output.appendLineOtherOut(
226+
`Connected session: ${effectiveMainKey}, ws://${wsHost}:${currentPort}`
227+
);
216228
replSession.updateReplSessionType();
217229

218-
outputWindow.setSession(mainSession, client.ns, mainKey);
230+
outputWindow.setSession(mainSession, client.ns, effectiveMainKey);
219231

220232
if (config.getConfig().autoEvaluateCode.onConnect.clj) {
221233
output.appendLineOtherOut(
222234
`Evaluating code from settings: 'calva.autoEvaluateCode.onConnect.clj'`
223235
);
224236
await evaluate.evaluateInOutputWindow(
225237
config.getConfig().autoEvaluateCode.onConnect.clj,
226-
mainKey,
238+
effectiveMainKey,
227239
outputWindow.getNs(),
228240
{}
229241
);
@@ -234,7 +246,12 @@ async function connectViaWebSocket(
234246
connectSequence.afterPrimaryReplConnectedCode ?? connectSequence.afterCLJReplJackInCode;
235247
if (afterMainReplCode) {
236248
output.appendLineOtherOut(`Evaluating 'afterPrimaryReplConnectedCode'`);
237-
await evaluate.evaluateInOutputWindow(afterMainReplCode, mainKey, outputWindow.getNs(), {});
249+
await evaluate.evaluateInOutputWindow(
250+
afterMainReplCode,
251+
effectiveMainKey,
252+
outputWindow.getNs(),
253+
{}
254+
);
238255
}
239256
if (!connectSequence.cljsType || connectSequence.cljsType === 'none') {
240257
output.maybePrintLegacyREPLWindowOutputMessage();
@@ -305,6 +322,9 @@ async function connectViaWebSocket(
305322
output.appendLineOtherOut('Browser REPL disconnected, waiting for reconnection...');
306323
state.connectionLogChannel().appendLine('Browser REPL disconnected');
307324

325+
// Preserve renames across browser reconnections
326+
preservedRenames = clientRegistry.getConnectionState(clientKey)?.renamedSessionNames;
327+
308328
// Clean up client and sessions but keep server alive
309329
clientTeardown.releaseClientSuffix(clientKey);
310330
const wasRegistered = clientRegistry.unregisterClient(clientKey);
@@ -466,6 +486,7 @@ async function connectToHost(
466486
connectSequence,
467487
baseSessionNames,
468488
suffix: resolution.suffix,
489+
renamedSessionNames: resolution.renamedSessionNames,
469490
},
470491
});
471492
localClient.addOnCloseHandler((c) => {
@@ -504,19 +525,26 @@ async function connectToHost(
504525
});
505526
clientRegistry.setCljcTargetForConnection(localClient.clientKey, 'primary');
506527

528+
// Apply preserved rename from previous connection
529+
const renamedPrimary = resolution.renamedSessionNames?.primary;
530+
if (renamedPrimary && renamedPrimary !== mainKey) {
531+
sessionRegistry.renameSession(mainKey, renamedPrimary);
532+
}
533+
const effectivePrimaryKey = renamedPrimary ?? mainKey;
534+
507535
status.update();
508-
output.appendLineOtherOut(`Connected session: ${mainKey}, port: ${port}`);
536+
output.appendLineOtherOut(`Connected session: ${effectivePrimaryKey}, port: ${port}`);
509537
replSession.updateReplSessionType();
510538

511-
outputWindow.setSession(mainSession, localClient.ns, mainKey);
539+
outputWindow.setSession(mainSession, localClient.ns, effectivePrimaryKey);
512540

513541
if (config.getConfig().autoEvaluateCode.onConnect.clj) {
514542
output.appendLineOtherOut(
515543
`Evaluating code from settings: 'calva.autoEvaluateCode.onConnect.clj'`
516544
);
517545
await evaluate.evaluateInOutputWindow(
518546
config.getConfig().autoEvaluateCode.onConnect.clj,
519-
mainKey,
547+
effectivePrimaryKey,
520548
outputWindow.getNs(),
521549
{}
522550
);
@@ -527,14 +555,19 @@ async function connectToHost(
527555
connectSequence.afterPrimaryReplConnectedCode ?? connectSequence.afterCLJReplJackInCode;
528556
if (afterMainReplCode) {
529557
output.appendLineOtherOut(`Evaluating 'afterPrimaryReplConnectedCode'`);
530-
await evaluate.evaluateInOutputWindow(afterMainReplCode, mainKey, outputWindow.getNs(), {});
558+
await evaluate.evaluateInOutputWindow(
559+
afterMainReplCode,
560+
effectivePrimaryKey,
561+
outputWindow.getNs(),
562+
{}
563+
);
531564
}
532565
if (!connectSequence.cljsType || connectSequence.cljsType === 'none') {
533566
output.maybePrintLegacyREPLWindowOutputMessage();
534567
}
535568
void output.replWindowAppendPrompt();
536569

537-
clojureDocs.probeAndSetSession(mainSession, mainKey);
570+
clojureDocs.probeAndSetSession(mainSession, effectivePrimaryKey);
538571

539572
let cljsSession = null,
540573
cljsBuild = null;
@@ -700,24 +733,34 @@ async function setUpCljsRepl(
700733
});
701734
clientRegistry.setCljcTargetForConnection(clientKey, 'secondary');
702735

703-
clojureDocs.probeAndSetSession(session, cljsKey);
736+
// Apply preserved rename from previous connection
737+
const renamedSecondary =
738+
clientRegistry.getConnectionState(clientKey)?.renamedSessionNames?.secondary;
739+
const effectiveCljsKey =
740+
renamedSecondary && renamedSecondary !== cljsKey
741+
? (sessionRegistry.renameSession(cljsKey, renamedSecondary), renamedSecondary)
742+
: cljsKey;
743+
744+
clojureDocs.probeAndSetSession(session, effectiveCljsKey);
704745

705746
status.update();
706-
output.appendLineOtherOut(`Connected session: ${cljsKey}${build ? ', repl: ' + build : ''}`);
747+
output.appendLineOtherOut(
748+
`Connected session: ${effectiveCljsKey}${build ? ', repl: ' + build : ''}`
749+
);
707750
outputWindow.appendLine(
708751
resultsOutputUtil.formatAsLineComments(outputWindow.CLJS_CONNECT_GREETINGS)
709752
);
710753
const description = await session.describe(true);
711754
const ns = description.aux?.['current-ns'] || 'user';
712755
await session.eval(`(in-ns '${ns})`, 'user').value;
713-
outputWindow.setSession(session, ns, cljsKey);
756+
outputWindow.setSession(session, ns, effectiveCljsKey);
714757
if (config.getConfig().autoEvaluateCode.onConnect.cljs) {
715758
output.appendLineOtherOut(
716759
`Evaluating code from settings: 'calva.autoEvaluateCode.onConnect.cljs'`
717760
);
718761
await evaluate.evaluateInOutputWindow(
719762
config.getConfig().autoEvaluateCode.onConnect.cljs,
720-
cljsKey,
763+
effectiveCljsKey,
721764
ns,
722765
{}
723766
);

0 commit comments

Comments
 (0)