Project Workspace view, abort button, session invalidation, workspace fixes

Workspace (/workspace/:projectId):
- Three-panel layout (tasks / chat / notes) with CSS grid collapse toggles
- WorkspaceTaskPanel: tasks grouped by milestone, collapsible groups, task
  detail slide-over with status cycling, TaskLogSection work log, and
  inline-confirm delete
- WorkspaceNoteEditor: list view (sorted by updated_at, inline tag pills,
  inline-confirm delete) with editor view (TipTap, TagInput, Suggest tags,
  60s autosave)
- Persistent workspace conversation stored in localStorage per project;
  reused on return visits
- Thinking enabled (think: true) with Reasoning block in streaming bubble
- workspace_project_id backend pipeline: chat.py → generation_task.py →
  llm.py; system prompt uses project title so agent passes project="Title"
  to tools (fixes create_note failing with numeric project string)
- SSE tool-call watcher bridges agent actions to panel updates
- Height fix: workspace-root uses height 100%; app-content switches to
  overflow hidden via :has() selector
- Entry point: "Open Workspace" button on ProjectView

Abort button:
- Stop button in ChatView header and WorkspaceView input bar
- Calls existing cancelGeneration() / POST .../generation/cancel

Session invalidation:
- POST /api/auth/invalidate-sessions bumps session_version, keeps current
  session alive; useful after SSO/OAuth password rotation
- Button in Settings → Active Sessions section

Other:
- Dashboard recent notes limit increased from 8 to 16
- Workspace chat abort replaces Send button while streaming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 11:34:06 -05:00
parent a8bb687349
commit 74ebb8a87f
15 changed files with 1927 additions and 9 deletions
+44
View File
@@ -21,6 +21,7 @@ const currentPassword = ref("");
const newPassword = ref("");
const confirmNewPassword = ref("");
const changingPassword = ref(false);
const invalidatingSessions = ref(false);
const saving = ref(false);
const saved = ref(false);
const exporting = ref(false);
@@ -155,6 +156,18 @@ async function changeEmail() {
}
}
async function invalidateSessions() {
invalidatingSessions.value = true;
try {
await apiPost("/api/auth/invalidate-sessions", {});
toastStore.show("All other sessions have been invalidated");
} catch {
toastStore.show("Failed to invalidate sessions", "error");
} finally {
invalidatingSessions.value = false;
}
}
async function changePassword() {
if (newPassword.value !== confirmNewPassword.value) {
toastStore.show("New passwords do not match", "error");
@@ -457,6 +470,20 @@ function hostname(url: string): string {
</div>
</section>
<!-- Invalidate Sessions half width -->
<section class="settings-section">
<h2>Active Sessions</h2>
<p class="section-desc">
Sign out all other devices and sessions. Use this after an SSO password change
to ensure stale sessions are revoked.
</p>
<div class="actions">
<button class="btn-danger-outline" @click="invalidateSessions" :disabled="invalidatingSessions">
{{ invalidatingSessions ? "Invalidating..." : "Invalidate All Other Sessions" }}
</button>
</div>
</section>
<!-- Change Password half width -->
<section class="settings-section">
<h2>Change Password</h2>
@@ -882,6 +909,23 @@ function hostname(url: string): string {
.btn-save:disabled { opacity: 0.6; cursor: default; }
.btn-save:hover:not(:disabled) { opacity: 0.9; }
.btn-danger-outline {
padding: 0.4rem 0.9rem;
background: none;
color: var(--color-danger, #e74c3c);
border: 1px solid var(--color-danger, #e74c3c);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.875rem;
font-family: inherit;
white-space: nowrap;
}
.btn-danger-outline:hover:not(:disabled) {
background: var(--color-danger, #e74c3c);
color: #fff;
}
.btn-danger-outline:disabled { opacity: 0.5; cursor: default; }
.btn-secondary {
padding: 0.4rem 0.9rem;
background: var(--color-bg-secondary);