mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
fix(tabs): grouped rail interaction fixes for inline rename
Two problems with renaming a tab in the vertical rail, both easier to hit now that the grouped rail has its own inline editor beside the session one. Writes. A committed rename PUT its name and only applied the answer if the same editor was still open when it came back. Reopening the editor before the PUT answered (F2 or right-click again, or starting a group rename, which cancels the session editor) threw the confirmed name away, so the tab kept showing the old name until an SSE frame happened to repaint it. Two quick renames also raced as two concurrent PUTs. Inline renames now go through a per-session queue: one PUT at a time in the order they were made, the confirmed name applied to app.sessions whatever happened to the editor, and the "already that name" check made when the write runs rather than when Enter is pressed, so confirming the name still on screen over a write in flight is a real write. Layout. The editor (a flex row) could not shrink below the input's intrinsic width, so a long w<n>-<case> prefix pushed the label past its row: the prefix slid out of view in the detailed rows and the input was clipped mid-word in the compact rail. The label now has min-width 0, the prefix gives way first (down to 2rem, with an ellipsis), the input keeps 4rem, and in the compact rail the row's adornments step aside while the name is edited. The detailed rows' three-line clamp also outranked the shared unclamp rule, which is what the existing "unclamped editor" browser test caught; it is restated there. Header strip, sidebar and flat-rail markup are unchanged. Tests (test/inline-rename.test.ts, browser suite): the unclamp check runs for simple and detailed rows; a write-ordering describe covers ordering, a reopened editor cancelled over a confirmed write, a re-sent unchanged name and a group rename taking over; a long-prefix describe drives real rows from a live session in simple, detailed and compact rails.
This commit is contained in:
@@ -2589,6 +2589,41 @@ Object.assign(CodemanApp.prototype, {
|
||||
return typeof confirmed === 'string' ? confirmed : name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Write an inline rename, one PUT per session at a time, in the order the
|
||||
* user made them. The editor can be reopened (or cancelled, or replaced by a
|
||||
* group rename) while a PUT is in flight, so the write lives here rather than
|
||||
* in the editor: a confirmed name is applied locally even after its editor is
|
||||
* gone, and the "already that name" check runs only once the earlier writes
|
||||
* have landed, so confirming the name still on screen is a real write.
|
||||
* Resolves { status: 'confirmed' | 'failed' | 'deleted' }; never rejects.
|
||||
*/
|
||||
_queueInlineSessionName(sessionId, desiredName) {
|
||||
this._inlineRenameWrites ??= new Map();
|
||||
const writes = this._inlineRenameWrites;
|
||||
const task = (writes.get(sessionId) || Promise.resolve()).then(async () => {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return { status: 'deleted' };
|
||||
if (session.name === desiredName) return { status: 'confirmed' };
|
||||
let confirmed = null;
|
||||
try {
|
||||
confirmed = await this._putSessionName(sessionId, desiredName);
|
||||
} catch {
|
||||
// A failure is a value, so a later write in the chain still runs.
|
||||
}
|
||||
if (!this.sessions.has(sessionId)) return { status: 'deleted' };
|
||||
if (confirmed === null) return { status: 'failed' };
|
||||
this._applyLocalSessionName(sessionId, confirmed);
|
||||
this.renderSessionTabs();
|
||||
return { status: 'confirmed' };
|
||||
});
|
||||
writes.set(sessionId, task);
|
||||
task.then(() => {
|
||||
if (writes.get(sessionId) === task) writes.delete(sessionId);
|
||||
});
|
||||
return task;
|
||||
},
|
||||
|
||||
async saveSessionName() {
|
||||
if (!this.editingSessionId) return;
|
||||
// Captured: the modal can be closed (or switched to another session) while
|
||||
@@ -2957,18 +2992,15 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (fullName === session.name) restoreOriginalChildren();
|
||||
else tabName.textContent = fullName || originalContent;
|
||||
|
||||
// Skip the API call if the session vanished between focus and blur.
|
||||
const stillExists = this.sessions.has(sessionId);
|
||||
if (stillExists && fullName !== session.name) {
|
||||
const confirmed = await this._putSessionName(sessionId, fullName);
|
||||
// Skip the API call if the session vanished between focus and blur. The
|
||||
// queue applies the confirmed name to this.sessions before the re-render
|
||||
// below repaints from it (see _applyLocalSessionName()).
|
||||
if (this.sessions.has(sessionId)) {
|
||||
const result = await this._queueInlineSessionName(sessionId, fullName);
|
||||
if (invalidated || this._activeRename !== renameHandle || !this.sessions.has(sessionId)) return;
|
||||
if (confirmed === null) {
|
||||
if (result.status === 'failed') {
|
||||
restoreOriginalChildren();
|
||||
this.showToast('Failed to rename', 'error');
|
||||
} else {
|
||||
// The re-render below repaints from this.sessions, so the new name has
|
||||
// to be in the map before it runs (see _applyLocalSessionName()).
|
||||
this._applyLocalSessionName(sessionId, confirmed);
|
||||
}
|
||||
}
|
||||
// Re-render tabs to restore full tab structure
|
||||
|
||||
@@ -1848,22 +1848,52 @@ html[data-tab-orientation='vertical'] .tab-rail .session-tab .tab-name-prefix {
|
||||
-webkit-line-clamp: unset;
|
||||
line-clamp: unset;
|
||||
overflow: visible;
|
||||
/* An input's intrinsic width counts toward the label's min-content, so with
|
||||
the default min-width:auto a long `w<n>-<case>` prefix plus the input
|
||||
pushed the label past its row: the prefix slid out of view and, in the
|
||||
compact rail, the input was clipped mid-word. */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* The prefix gives way first, down to a stub with an ellipsis. */
|
||||
:is(
|
||||
html[data-tab-orientation='vertical'] .tab-rail,
|
||||
html[data-session-list='sidebar'] .session-sidebar
|
||||
) .tab-name-renaming .tab-rename-prefix {
|
||||
flex: 0 0 auto;
|
||||
flex: 0 1 auto;
|
||||
min-width: 2rem;
|
||||
max-width: 50%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* The input always keeps room to type. `!important` beats the editor's inline
|
||||
`min-width: 0`, which the header strip's fixed-width editor relies on. */
|
||||
:is(
|
||||
html[data-tab-orientation='vertical'] .tab-rail,
|
||||
html[data-session-list='sidebar'] .session-sidebar
|
||||
) .tab-name-renaming .tab-rename-input {
|
||||
flex: 1 1 0;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
min-width: 4rem !important;
|
||||
}
|
||||
|
||||
/* A compact rail row has no room for both the editor and its adornments, so
|
||||
they step aside while the name is being edited (the re-render that ends the
|
||||
edit brings them back). */
|
||||
html[data-tab-orientation='vertical'].tab-rail-compact
|
||||
.tab-rail
|
||||
.session-tab:has(.tab-name-renaming)
|
||||
:is(
|
||||
.tab-mode,
|
||||
.tab-exited-badge,
|
||||
.tab-detached-badge,
|
||||
.tab-badge,
|
||||
.tab-subagent-badge,
|
||||
.tab-ultracode-badge,
|
||||
.tab-actions
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab folder path — hidden by default, shown via .tabs-show-folder on container */
|
||||
@@ -18895,6 +18925,13 @@ html[data-tab-orientation='vertical'][data-tab-rail-detail='rich']:not(.tab-rail
|
||||
line-clamp: 3;
|
||||
}
|
||||
|
||||
/* ...except while it is being edited: this rule outranks the shared renaming
|
||||
rule's unclamp, so restate it (the editor is a flex row, never clamped). */
|
||||
html[data-tab-orientation='vertical'][data-tab-rail-detail='rich']:not(.tab-rail-compact) .tab-rail .session-tab .tab-name.tab-name-renaming {
|
||||
-webkit-line-clamp: unset;
|
||||
line-clamp: unset;
|
||||
}
|
||||
|
||||
html[data-tab-orientation='vertical'][data-tab-rail-detail='rich']:not(.tab-rail-compact) .tab-rail .session-tab .tab-folder {
|
||||
font-size: 0.66rem;
|
||||
margin-top: 0.1rem;
|
||||
|
||||
Reference in New Issue
Block a user