Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
131f1e9
feat(connections): let a connection be tried before it has to be save…
deveshk0 Sep 8, 2026
8bc5168
fix(connections): close the three ways the save offer could mislead (…
deveshk0 Sep 8, 2026
64b48e4
fix(connections): give back a connection nobody is waiting for (#369 …
deveshk0 Sep 8, 2026
4030f18
test(indexes): wait for the schema fields, not just for the call (#36…
deveshk0 Sep 8, 2026
9f1edcc
fix(connections): stop an unsaved connection leaking into places it c…
deveshk0 Sep 8, 2026
da23591
fix(connections): keep an unsaved tab out of the mirrored split, and …
deveshk0 Sep 8, 2026
204169b
Revert "split_pane" half of da23591 — it needs a product decision, no…
deveshk0 Sep 8, 2026
41887aa
fix(connections): let the offer describe the connection that was made…
deveshk0 Sep 8, 2026
9f31a2f
fix(sidebar): refuse a shortcut that could not survive a restart (#36…
deveshk0 Sep 8, 2026
7c49485
fix(connections): show the URI that was connected, and decide shortcu…
deveshk0 Sep 8, 2026
c866ab2
fix(connections): stop an unsaved session answering to a saved profil…
deveshk0 Sep 8, 2026
c5bc1fb
fix(connections): keep an unsaved session out of a saved profile's qu…
deveshk0 Sep 8, 2026
a377e30
fix(connections): take Math.random out of an id that now keys storage…
deveshk0 Sep 8, 2026
b6d91fd
fix(connections): finish isolating an unsaved session's query, chat a…
deveshk0 Sep 8, 2026
6b48634
fix(connections): let only the current attempt release the Connect bu…
deveshk0 Sep 8, 2026
7385899
fix(connections): isolate history writes and the shell assistant, and…
deveshk0 Sep 8, 2026
1a08ba1
fix(connections): read ephemeral from the profile, and release a sess…
deveshk0 Sep 8, 2026
6d29038
fix(connections): make the post-await ownership checks actually able …
deveshk0 Sep 8, 2026
aa66bd5
fix(connections): retarget a trial connection's chats under its own i…
deveshk0 Sep 8, 2026
c942548
fix(connections): stop the name suggestion renaming a profile called …
deveshk0 Sep 9, 2026
29c33ff
fix(connections): advance the attempt generation when the editor clos…
deveshk0 Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import {
} from './workspace/workspaceStore';
import {
toPersistedTab,
isEphemeralProfileId,
toDisconnectedSnapshot,
rebindConnection,
toProfileSpaceId,
Expand Down Expand Up @@ -1127,7 +1128,13 @@ function Workspace() {

const handleQuickConnect = async (profile: ConnectionProfile): Promise<string | null> => {
const existing = activeConnections.find(
(c) => c.profileId === profile.id || c.name === profile.name,
(c) =>
c.profileId === profile.id ||
// Matching on name is a convenience for a profile reconnecting under a
// new session id. An unsaved connection must not answer to it: its name
// is editable and can be set to this profile's, and quick-connect would
// then hand back a session pointing at a different server (#369 review).
(c.name === profile.name && !isEphemeralProfileId(c.profileId)),
);
if (existing) return existing.id;
try {
Expand Down Expand Up @@ -1434,6 +1441,25 @@ function Workspace() {
const connectionNameFor = (connectionId: string): string =>
activeConnections.find((c) => c.id === connectionId)?.name || connectionId;

/**
* The key a connection's saved queries, default query and history live under.
*
* Normally the display name, which is what these stores have always been
* keyed on. An unsaved connection gets its own ephemeral id instead: its name
* is editable and can be a saved profile's, and sharing a namespace would
* mean opening a collection ran the saved profile's default query against the
* trial server, while the trial's own history wrote back into the saved
* profile's (#369 review).
*
* Its entries are orphaned when the session ends, which is the right outcome
* for a connection the user declined to keep.
*/
const connectionQueryKeyFor = (connectionId: string): string => {
const conn = activeConnections.find((c) => c.id === connectionId);
if (!conn) return connectionId;
return isEphemeralProfileId(conn.profileId) ? conn.profileId! : conn.name;
Comment thread
deveshk0 marked this conversation as resolved.
Comment thread
deveshk0 marked this conversation as resolved.
};

// Never sit on a blank canvas — if every tab is closed, bring back Quick
// Start. Main-window-only (Phase 3 Task 4): a secondary window has no
// quickstart concept — the spec says an emptied secondary window closes
Expand Down Expand Up @@ -1576,7 +1602,7 @@ function Workspace() {
let def: QueryDef | null = (savedQuery as QueryDef | undefined) ?? null;
if (!def) {
try {
const cq = await loadCollectionQueries(connectionNameFor(connectionId), dbName, collName);
const cq = await loadCollectionQueries(connectionQueryKeyFor(connectionId), dbName, collName);
def = (cq.default as QueryDef | null) ?? null;
} catch {
def = null;
Expand Down Expand Up @@ -3131,7 +3157,15 @@ function Workspace() {
if (!matchesNamespaceScope(paletteNamespaceScope, { connectionName, db: t.db, collection: t.collection })) {
return;
}
const cq = await loadCollectionQueries(connectionName, t.db, t.collection);
// Keyed like every other reader of this store. The scope match above
// stays on the display name — that is what the user typed — but the
// load must not, or the palette lists a saved profile's queries as
// actions bound to a trial tab and runs them there (#369 review).
const cq = await loadCollectionQueries(
connectionQueryKeyFor(t.connectionId),
t.db,
t.collection,
);
for (const s of cq.saved) {
items.push({
id: `saved:${t.id}:${s.id}`,
Expand Down Expand Up @@ -4520,6 +4554,7 @@ function Workspace() {
key={tab.id}
connectionId={tab.connectionId}
connectionName={connectionName}
queryStoreKey={connectionQueryKeyFor(tab.connectionId)}
Comment thread
deveshk0 marked this conversation as resolved.
connectionUser={connectionUser}
databaseName={tab.db}
collectionName={tab.collection}
Expand Down
22 changes: 19 additions & 3 deletions src/components/AIChatPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ interface AIChatPanelProps {
connectionId?: string;
/** Connection display name — scopes History + session with db/collection. */
connectionName?: string;
/**
* Identity for chat history, when it differs from the display name.
*
* A connection the user never saved can carry a saved profile's name (#364),
* and `connectionName` alone would then list, write into and treat as local
* that profile's conversations — including running their generated queries
* against the trial server. Defaults to `connectionName`, which is what this
* was scoped on before the distinction existed.
*/
scopeKey?: string;
Comment thread
deveshk0 marked this conversation as resolved.
databaseName?: string;
collectionName: string;
fields?: string[];
Expand Down Expand Up @@ -278,6 +288,7 @@ const composerClassName = cn(
export const AIChatPanel: React.FC<AIChatPanelProps> = ({
connectionId,
connectionName,
scopeKey,
databaseName,
collectionName,
fields = [],
Expand Down Expand Up @@ -755,14 +766,19 @@ export const AIChatPanel: React.FC<AIChatPanelProps> = ({
}, []);
const nextChatId = () => `m${chatIdRef.current++}`;

// The namespace conversations belong to. Deliberately NOT what is sent to the
// agent as context further down — that stays the real connection name, since
// it is prose for a model rather than a key.
const chatScopeName = scopeKey ?? connectionName ?? '';

const scope: ChatScope = useMemo(
() => ({
connectionName: connectionName ?? '',
connectionName: chatScopeName,
database: databaseName ?? '',
collection: collectionName,
variant,
}),
[connectionName, databaseName, collectionName, variant]
[chatScopeName, databaseName, collectionName, variant]
);

// The chat this panel is writing to. A tab that has never had one gets an id
Expand All @@ -783,7 +799,7 @@ export const AIChatPanel: React.FC<AIChatPanelProps> = ({
* would target this collection with someone else's query. */
const foreignChat =
openScope !== null &&
(openScope.connectionName !== (connectionName ?? '') ||
(openScope.connectionName !== chatScopeName ||
openScope.database !== (databaseName ?? '') ||
openScope.collection !== collectionName ||
openScope.variant !== variant);
Expand Down
Loading