refactor(ui): the remaining views migrate; 2 dead classes, 2 off-palette hovers
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 30s
CI & Build / TypeScript typecheck (push) Successful in 45s
CI & Build / integration (push) Successful in 1m50s
CI & Build / Python tests (push) Successful in 2m13s
CI & Build / Build & push image (push) Successful in 42s

Twelve more files onto the shared buttons. What the pass turned up:

DEAD, verified not merely unnamed:
- .btn-reconsolidate (TaskEditorView). A comment eleven hundred lines up in the
  same file says the feature was removed in Phase 8. The CSS outlived it.
- .btn-remove-slot (SettingsView), style rules only, no template anywhere.

OFF-PALETTE, the #2319 shape: TrashView's restore and purge hovers used
`var(--color-primary, #6366f1)` and `var(--color-danger, #ef4444)` — Tailwind
indigo and Tailwind red, from no palette in this system. The fallback is what
renders if the token is ever absent, and it renders something plausible
forever. Now the action and destructive colours, no fallback.

A REAL BREAKAGE MY OWN CHECK COULD NOT SEE, worth recording. Deleting a rule
whose selector was part of a comma-separated group left the leading selectors
behind:

    .btn-log-edit,
    <nothing>
    .log-textarea { … }

which silently swallows the next rule. Brace counting passed — there are no
braces in a dangling fragment. Found by scanning for selector lines ending in
`,` not followed by another selector; three instances across two files, one of
them interleaved with comments so the first sweep missed it. The sweep is now
part of the verification, not a one-off.

Kept bespoke, deliberately: .btn-pin/.btn-unpin (pill-shaped history badges),
.btn-add-share and .btn-new-note (gradient CTAs — brand moments, which the
house style does sanction), .btn-icon/.btn-bell (icon buttons, a different
component), .btn-add-system/.btn-add-milestone (dashed "add" affordances).
These are not drift; they are other things wearing a btn- prefix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
This commit is contained in:
2026-08-01 22:53:13 -04:00
co-authored by Claude Opus 5
parent 3fc693443e
commit 5f8b824523
12 changed files with 45 additions and 638 deletions
+1 -14
View File
@@ -281,7 +281,7 @@ onMounted(loadVersions);
<div class="history-footer"> <div class="history-footer">
<button <button
class="btn-restore" class="btn-primary"
:disabled="!selectedVersion?.body" :disabled="!selectedVersion?.body"
@click="restore" @click="restore"
>Restore this version</button> >Restore this version</button>
@@ -393,19 +393,6 @@ onMounted(loadVersions);
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
} }
.btn-restore {
padding: 0.45rem 1rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.9rem;
}
.btn-restore:disabled {
opacity: 0.5;
cursor: default;
}
/* ── Pin badges + label rendering ───────────────────────────────────────── */ /* ── Pin badges + label rendering ───────────────────────────────────────── */
.pin-badge { .pin-badge {
+2 -35
View File
@@ -50,7 +50,7 @@ onMounted(() => store.fetchAll())
<span class="notif-panel-title">Notifications</span> <span class="notif-panel-title">Notifications</span>
<button <button
v-if="store.count > 0" v-if="store.count > 0"
class="btn-mark-all" class="btn-text"
@click="store.markAll()" @click="store.markAll()"
>Mark all read</button> >Mark all read</button>
</header> </header>
@@ -70,7 +70,7 @@ onMounted(() => store.fetchAll())
</p> </p>
<span class="notif-time">{{ relativeTime(n.created_at) }}</span> <span class="notif-time">{{ relativeTime(n.created_at) }}</span>
</div> </div>
<button class="btn-notif-close" @click.stop="store.markRead(n.id)" aria-label="Dismiss"><X :size="16" /></button> <button class="btn-text" @click.stop="store.markRead(n.id)" aria-label="Dismiss"><X :size="16" /></button>
</li> </li>
</ul> </ul>
<div v-else class="notif-empty">No unread notifications</div> <div v-else class="notif-empty">No unread notifications</div>
@@ -108,21 +108,6 @@ onMounted(() => store.fetchAll())
font-size: 0.9rem; font-size: 0.9rem;
} }
.btn-mark-all {
background: none;
border: none;
color: var(--color-primary);
font-size: 0.78rem;
cursor: pointer;
padding: 0;
}
.btn-mark-all:hover { text-decoration: underline; }
.notif-list {
list-style: none;
padding: 0;
margin: 0;
}
.notif-item { .notif-item {
display: flex; display: flex;
@@ -148,22 +133,4 @@ onMounted(() => store.fetchAll())
} }
.notif-time { font-size: 0.75rem; color: var(--color-muted); } .notif-time { font-size: 0.75rem; color: var(--color-muted); }
.btn-notif-close {
background: none;
border: none;
color: var(--color-muted);
cursor: pointer;
font-size: 0.8rem;
padding: 0.1rem 0.25rem;
flex-shrink: 0;
transition: color 0.1s;
}
.btn-notif-close:hover { color: var(--color-text); }
.notif-empty {
padding: 1.5rem;
text-align: center;
color: var(--color-muted);
font-size: 0.88rem;
}
</style> </style>
+2 -38
View File
@@ -117,7 +117,7 @@ onMounted(async () => {
<div class="share-dialog" role="dialog" :aria-label="`Share ${resourceTitle}`"> <div class="share-dialog" role="dialog" :aria-label="`Share ${resourceTitle}`">
<header class="share-header"> <header class="share-header">
<h2 class="share-title">Share "{{ resourceTitle }}"</h2> <h2 class="share-title">Share "{{ resourceTitle }}"</h2>
<button class="btn-close" @click="emit('close')" aria-label="Close"><X :size="16" /></button> <button class="btn-text" @click="emit('close')" aria-label="Close"><X :size="16" /></button>
</header> </header>
<!-- Add share form --> <!-- Add share form -->
@@ -184,7 +184,7 @@ onMounted(async () => {
<option value="editor">Editor</option> <option value="editor">Editor</option>
<option value="admin">Admin</option> <option value="admin">Admin</option>
</select> </select>
<button class="btn-remove-share" @click="removeShare(share)" aria-label="Remove"><X :size="16" /></button> <button class="btn-text" @click="removeShare(share)" aria-label="Remove"><X :size="16" /></button>
</li> </li>
<li v-if="!shares.length" class="shares-empty">Not shared with anyone yet</li> <li v-if="!shares.length" class="shares-empty">Not shared with anyone yet</li>
</ul> </ul>
@@ -231,23 +231,6 @@ onMounted(async () => {
color: var(--color-text); color: var(--color-text);
} }
.btn-close {
background: none;
border: none;
color: var(--color-text-muted);
cursor: pointer;
font-size: 1.1rem;
padding: 0.25rem;
line-height: 1;
border-radius: 4px;
transition: color 0.15s;
}
.btn-close:hover { color: var(--color-text); }
.share-add {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--color-border);
}
.share-tabs { .share-tabs {
display: flex; display: flex;
@@ -394,23 +377,4 @@ onMounted(async () => {
cursor: pointer; cursor: pointer;
} }
.btn-remove-share {
background: none;
border: none;
color: var(--color-text-muted);
cursor: pointer;
font-size: 0.9rem;
padding: 0.15rem 0.3rem;
border-radius: 4px;
transition: color 0.15s;
flex-shrink: 0;
}
.btn-remove-share:hover { color: var(--color-danger, #ef4444); }
.shares-empty {
color: var(--color-text-muted);
font-size: 0.85rem;
text-align: center;
padding: 0.75rem;
}
</style> </style>
+6 -51
View File
@@ -163,7 +163,7 @@ async function confirmDelete() {
<!-- Toolbar --> <!-- Toolbar -->
<div class="systems-toolbar"> <div class="systems-toolbar">
<button v-if="!showCreate" class="btn-add-system" @click="openCreate"> <button v-if="!showCreate" class="btn-ghost btn-inline btn-add-system" @click="openCreate">
+ System + System
</button> </button>
<label v-if="archivedSystems.length" class="archived-toggle"> <label v-if="archivedSystems.length" class="archived-toggle">
@@ -190,10 +190,10 @@ async function confirmDelete() {
aria-label="System description" aria-label="System description"
></textarea> ></textarea>
<div class="system-form-actions"> <div class="system-form-actions">
<button type="submit" class="btn-confirm" :disabled="!newName.trim() || creating"> <button type="submit" class="btn-primary btn-compact" :disabled="!newName.trim() || creating">
{{ creating ? "Creating…" : "Create" }} {{ creating ? "Creating…" : "Create" }}
</button> </button>
<button type="button" class="btn-cancel" @click="cancelCreate">Cancel</button> <button type="button" class="btn-ghost btn-compact" @click="cancelCreate">Cancel</button>
</div> </div>
</form> </form>
@@ -211,7 +211,7 @@ async function confirmDelete() {
<div v-else-if="!visibleSystems.length" class="systems-empty"> <div v-else-if="!visibleSystems.length" class="systems-empty">
<p class="empty-title">No systems yet</p> <p class="empty-title">No systems yet</p>
<p class="empty-sub">Define a reusable subsystem or area to organize issues against.</p> <p class="empty-sub">Define a reusable subsystem or area to organize issues against.</p>
<button v-if="!showCreate" class="btn-confirm" @click="openCreate">+ Create a system</button> <button v-if="!showCreate" class="btn-primary btn-compact" @click="openCreate">+ Create a system</button>
</div> </div>
<!-- List --> <!-- List -->
@@ -241,10 +241,10 @@ async function confirmDelete() {
aria-label="System description" aria-label="System description"
></textarea> ></textarea>
<div class="system-form-actions"> <div class="system-form-actions">
<button type="submit" class="btn-confirm" :disabled="!editName.trim() || savingEdit"> <button type="submit" class="btn-primary btn-compact" :disabled="!editName.trim() || savingEdit">
{{ savingEdit ? "Saving…" : "Save" }} {{ savingEdit ? "Saving…" : "Save" }}
</button> </button>
<button type="button" class="btn-cancel" @click="cancelEdit">Cancel</button> <button type="button" class="btn-ghost btn-compact" @click="cancelEdit">Cancel</button>
</div> </div>
</form> </form>
</template> </template>
@@ -387,51 +387,6 @@ async function confirmDelete() {
.system-textarea { resize: vertical; } .system-textarea { resize: vertical; }
.system-form-actions { display: flex; gap: 0.4rem; } .system-form-actions { display: flex; gap: 0.4rem; }
.btn-confirm {
padding: 0.35rem 0.8rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.82rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-confirm:hover:not(:disabled) { background: var(--color-action-primary-hover); }
.btn-confirm:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.btn-confirm:disabled { opacity: 0.5; cursor: default; }
.btn-cancel {
padding: 0.35rem 0.8rem;
background: var(--color-action-secondary);
border: none;
color: var(--fs-text-on-action);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.82rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-cancel:hover { background: var(--color-action-secondary-hover); }
.btn-cancel:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* ── List ─────────────────────────────────────────────────────── */
.systems-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.system-card {
display: flex;
align-items: flex-start;
gap: 0.65rem;
padding: 0.65rem 0.85rem;
background: var(--color-bg-card);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
transition: border-color 0.12s, box-shadow 0.15s;
}
.system-card:hover {
border-color: color-mix(in srgb, var(--color-primary) 50%, var(--color-border));
box-shadow: 0 3px 10px rgba(0,0,0,0.07);
}
.system-card--archived { opacity: 0.6; } .system-card--archived { opacity: 0.6; }
.system-swatch { .system-swatch {
+5 -58
View File
@@ -122,8 +122,8 @@ onMounted(loadLogs);
class="log-duration-input" class="log-duration-input"
placeholder="min" placeholder="min"
/> />
<button class="btn-log-save" @click="saveEdit(log)">Save</button> <button class="btn-primary btn-compact" @click="saveEdit(log)">Save</button>
<button class="btn-log-cancel" @click="cancelEdit">Cancel</button> <button class="btn-ghost btn-compact" @click="cancelEdit">Cancel</button>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@@ -133,8 +133,8 @@ onMounted(loadLogs);
{{ formatDuration(log.duration_minutes) }} {{ formatDuration(log.duration_minutes) }}
</span> </span>
<div class="log-entry-actions"> <div class="log-entry-actions">
<button class="btn-log-edit" @click="startEdit(log)" title="Edit">Edit</button> <button class="btn-text" @click="startEdit(log)" title="Edit">Edit</button>
<button class="btn-log-delete" aria-label="Delete log entry" @click="deleteLog(log)">&times;</button> <button class="btn-text" aria-label="Delete log entry" @click="deleteLog(log)">&times;</button>
</div> </div>
</div> </div>
<div class="log-content prose" v-html="renderMarkdown(log.content)"></div> <div class="log-content prose" v-html="renderMarkdown(log.content)"></div>
@@ -162,7 +162,7 @@ onMounted(loadLogs);
/> />
</label> </label>
<button <button
class="btn-log-submit" class="btn-primary btn-compact"
@click="submitLog" @click="submitLog"
:disabled="!newContent.trim() || submitting" :disabled="!newContent.trim() || submitting"
> >
@@ -230,34 +230,7 @@ onMounted(loadLogs);
gap: 0.25rem; gap: 0.25rem;
} }
.btn-log-edit,
.btn-log-delete {
background: none;
border: none;
cursor: pointer;
color: var(--color-text-muted);
font-size: 0.8rem;
font-family: inherit;
padding: 0.1rem 0.25rem;
line-height: 1;
}
.btn-log-edit:hover { color: var(--color-primary); }
.btn-log-delete:hover { color: var(--color-danger, #e74c3c); }
.log-content {
font-size: 0.875rem;
}
.log-content :deep(p) { margin: 0; }
.log-add {
border-top: 1px solid var(--color-border);
padding-top: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.log-textarea { .log-textarea {
width: 100%; width: 100%;
@@ -307,32 +280,6 @@ onMounted(loadLogs);
border-color: var(--color-primary); border-color: var(--color-primary);
} }
.btn-log-submit,
.btn-log-save {
margin-left: auto;
padding: 0.3rem 0.75rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
font-family: inherit;
}
.btn-log-submit:disabled {
opacity: 0.5;
cursor: default;
}
.btn-log-cancel {
padding: 0.3rem 0.6rem;
background: none;
border: 1px solid var(--color-border);
color: var(--color-text-secondary);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
font-family: inherit;
}
</style> </style>
+3 -39
View File
@@ -381,7 +381,7 @@ onUnmounted(() => {
<option value="alpha">Alphabetical</option> <option value="alpha">Alphabetical</option>
<option value="type">By type</option> <option value="type">By type</option>
</select> </select>
<button class="btn-graph" :class="{ active: graphOpen }" @click="toggleGraph" title="Toggle graph view"> <button class="btn-ghost btn-compact" :class="{ active: graphOpen }" @click="toggleGraph" title="Toggle graph view">
<Share2 :size="16" /> <Share2 :size="16" />
Graph Graph
</button> </button>
@@ -463,14 +463,14 @@ onUnmounted(() => {
<span>Graph</span> <span>Graph</span>
<div style="display:flex;gap:4px;align-items:center"> <div style="display:flex;gap:4px;align-items:center">
<button <button
class="btn-icon-sm" class="btn-text"
@click="toggleGraphExpand" @click="toggleGraphExpand"
:title="graphExpanded ? 'Narrow panel' : 'Expand panel'" :title="graphExpanded ? 'Narrow panel' : 'Expand panel'"
> >
<ChevronLeft v-if="graphExpanded" :size="16" /> <ChevronLeft v-if="graphExpanded" :size="16" />
<ChevronRight v-else :size="16" /> <ChevronRight v-else :size="16" />
</button> </button>
<button class="btn-icon-sm" @click="toggleGraph" title="Close graph"> <button class="btn-text" @click="toggleGraph" title="Close graph">
<X :size="16" /> <X :size="16" />
</button> </button>
</div> </div>
@@ -719,26 +719,6 @@ onUnmounted(() => {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
} }
.btn-graph {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 12px;
border-radius: 8px;
border: 1px solid var(--color-border, rgba(255,255,255,0.1));
background: transparent;
color: var(--color-muted);
cursor: pointer;
font-size: 0.85rem;
transition: all 0.15s;
white-space: nowrap;
}
.btn-graph:hover { color: var(--color-text); border-color: rgba(255,255,255,0.2); }
.btn-graph.active {
background: var(--color-primary-wash);
border-color: rgba(91, 74, 138, 0.35);
color: var(--color-primary);
}
/* ── Card grid ───────────────────────────────────────────── */ /* ── Card grid ───────────────────────────────────────────── */
.card-grid { .card-grid {
@@ -956,22 +936,6 @@ onUnmounted(() => {
border-bottom: 1px solid var(--color-border, rgba(255,255,255,0.06)); border-bottom: 1px solid var(--color-border, rgba(255,255,255,0.06));
flex-shrink: 0; flex-shrink: 0;
} }
.btn-icon-sm {
background: none;
border: none;
color: var(--color-muted);
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.85rem;
transition: color 0.15s;
}
.btn-icon-sm:hover { color: var(--color-text); }
.graph-embed {
flex: 1;
overflow: hidden;
position: relative;
}
/* Override GraphView's 100vh height so it fills the panel instead */ /* Override GraphView's 100vh height so it fills the panel instead */
.graph-embed :deep(.graph-page) { .graph-embed :deep(.graph-page) {
height: 100%; height: 100%;
+1 -14
View File
@@ -177,7 +177,7 @@ function clearFilters() {
<input v-model="dateTo" type="date" class="filter-date" title="To date" /> <input v-model="dateTo" type="date" class="filter-date" title="To date" />
<button <button
v-if="category || search || dateFrom || dateTo" v-if="category || search || dateFrom || dateTo"
class="btn-clear" class="btn-ghost btn-compact"
@click="clearFilters" @click="clearFilters"
> >
Clear Clear
@@ -337,19 +337,6 @@ function clearFilters() {
.filter-date { .filter-date {
width: 140px; width: 140px;
} }
.btn-clear {
padding: 0.4rem 0.75rem;
background: transparent;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
}
.btn-clear:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
}
/* Table */ /* Table */
.loading-msg, .loading-msg,
+4 -75
View File
@@ -194,22 +194,22 @@ async function convertToTask() {
</div> </div>
<template v-else-if="store.currentNote"> <template v-else-if="store.currentNote">
<div class="toolbar"> <div class="toolbar">
<router-link to="/notes" class="btn-back"> Notes</router-link> <router-link to="/notes" class="btn-ghost"> Notes</router-link>
<router-link <router-link
:to="`/notes/${store.currentNote.id}/edit`" :to="`/notes/${store.currentNote.id}/edit`"
class="btn-edit" class="btn-primary"
> >
Edit Edit
</router-link> </router-link>
<button <button
v-if="!store.currentNote.is_task" v-if="!store.currentNote.is_task"
class="btn-convert" class="btn-secondary btn-compact"
@click="convertToTask" @click="convertToTask"
:disabled="converting" :disabled="converting"
> >
{{ converting ? "Converting..." : "Convert to Task" }} {{ converting ? "Converting..." : "Convert to Task" }}
</button> </button>
<button class="btn-share" @click="showShare = true">Share</button> <button class="btn-secondary btn-compact" @click="showShare = true">Share</button>
</div> </div>
<!-- Breadcrumb: parent project milestone --> <!-- Breadcrumb: parent project milestone -->
@@ -329,81 +329,10 @@ async function convertToTask() {
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.btn-back {
display: inline-flex;
align-items: center;
padding: 0.45rem 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: none;
color: var(--color-text-secondary);
text-decoration: none;
cursor: pointer;
font-size: 0.9rem;
}
.btn-back:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
/* Edit: Moss action-primary — switching from view to edit is operating /* Edit: Moss action-primary — switching from view to edit is operating
the software, not a brand moment. */ the software, not a brand moment. */
.btn-edit {
display: inline-flex;
align-items: center;
padding: 0.45rem 1.1rem;
border: none;
border-radius: var(--radius-sm);
background: var(--color-action-primary);
color: var(--fs-text-on-action);
text-decoration: none;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.15s;
}
.btn-edit:hover {
background: var(--color-action-primary-hover);
color: var(--fs-text-on-action);
}
/* Convert + Share: Bronze action-secondary — alternate paths */ /* Convert + Share: Bronze action-secondary — alternate paths */
.btn-convert {
margin-left: auto;
padding: 0.3rem 0.75rem;
background: var(--color-action-secondary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
transition: background 0.15s;
}
.btn-convert:hover { background: var(--color-action-secondary-hover); }
.btn-convert:disabled {
opacity: 0.6;
cursor: default;
}
.btn-share {
padding: 0.3rem 0.75rem;
background: var(--color-action-secondary);
border: none;
border-radius: var(--radius-sm);
color: var(--fs-text-on-action);
cursor: pointer;
font-size: 0.85rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-share:hover { background: var(--color-action-secondary-hover); }
.note-title {
font-family: "Fraunces", Georgia, serif;
font-size: 2rem;
font-weight: 500;
line-height: 1.2;
margin: 0.25rem 0 0.5rem;
color: var(--color-text);
}
.meta { .meta {
display: flex; display: flex;
+7 -145
View File
@@ -435,7 +435,7 @@ async function confirmDelete() {
<!-- Nav bar --> <!-- Nav bar -->
<div class="page-header"> <div class="page-header">
<router-link to="/projects" class="btn-back"> Projects</router-link> <router-link to="/projects" class="btn-ghost"> Projects</router-link>
<div class="page-header-actions"> <div class="page-header-actions">
<template v-if="showStartPlanning"> <template v-if="showStartPlanning">
<input <input
@@ -451,7 +451,7 @@ async function confirmDelete() {
> >
Create plan Create plan
</button> </button>
<button class="btn-share" @click="showStartPlanning = false; planTitle = ''">Cancel</button> <button class="btn-secondary btn-compact" @click="showStartPlanning = false; planTitle = ''">Cancel</button>
</template> </template>
<button <button
v-else-if="project" v-else-if="project"
@@ -464,7 +464,7 @@ async function confirmDelete() {
<LayoutGrid :size="16" /> <LayoutGrid :size="16" />
Workspace Workspace
</router-link> </router-link>
<button v-if="project && !showStartPlanning" class="btn-share" @click="showShare = true">Share</button> <button v-if="project && !showStartPlanning" class="btn-secondary btn-compact" @click="showShare = true">Share</button>
<button v-if="project && !showStartPlanning" class="btn-danger-outline" @click="showDeleteConfirm = true">Delete</button> <button v-if="project && !showStartPlanning" class="btn-danger-outline" @click="showDeleteConfirm = true">Delete</button>
</div> </div>
</div> </div>
@@ -548,7 +548,7 @@ async function confirmDelete() {
<option v-for="ds in designSystems" :key="ds.id" :value="ds.id">{{ ds.title }}</option> <option v-for="ds in designSystems" :key="ds.id" :value="ds.id">{{ ds.title }}</option>
</select> </select>
</div> </div>
<button class="btn-save-panel" @click="saveProject" :disabled="!editDirty || saving"> <button class="btn-primary" @click="saveProject" :disabled="!editDirty || saving">
{{ saving ? "Saving..." : "Save Changes" }} {{ saving ? "Saving..." : "Save Changes" }}
</button> </button>
</aside> </aside>
@@ -581,7 +581,7 @@ async function confirmDelete() {
</div> </div>
<template v-else> <template v-else>
<div class="milestone-actions"> <div class="milestone-actions">
<button v-if="!showNewMilestone" class="btn-add-milestone" @click="showNewMilestone = true"> <button v-if="!showNewMilestone" class="btn-ghost btn-inline btn-add-milestone" @click="showNewMilestone = true">
+ Milestone + Milestone
</button> </button>
<div v-else class="new-milestone-row"> <div v-else class="new-milestone-row">
@@ -593,10 +593,10 @@ async function confirmDelete() {
@keydown.enter="createMilestone" @keydown.enter="createMilestone"
@keydown.escape="showNewMilestone = false; newMilestoneTitle = ''" @keydown.escape="showNewMilestone = false; newMilestoneTitle = ''"
/> />
<button class="btn-ms-confirm" @click="createMilestone" :disabled="!newMilestoneTitle.trim() || creatingMilestone"> <button class="btn-primary btn-compact" @click="createMilestone" :disabled="!newMilestoneTitle.trim() || creatingMilestone">
{{ creatingMilestone ? "..." : "Add" }} {{ creatingMilestone ? "..." : "Add" }}
</button> </button>
<button class="btn-ms-cancel" @click="showNewMilestone = false; newMilestoneTitle = ''">Cancel</button> <button class="btn-secondary btn-compact" @click="showNewMilestone = false; newMilestoneTitle = ''">Cancel</button>
</div> </div>
</div> </div>
@@ -856,80 +856,9 @@ async function confirmDelete() {
min-width: 200px; min-width: 200px;
} }
.btn-back {
display: inline-flex;
align-items: center;
padding: 0.4rem 0.9rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: none;
color: var(--color-text-secondary);
text-decoration: none;
font-size: 0.875rem;
transition: border-color 0.15s, color 0.15s;
}
.btn-back:hover { border-color: var(--color-primary); color: var(--color-primary); }
/* Open Workspace: brand-moment CTA — keep accent gradient. Workspace is
the project's "central feature moment" — entering the focused workspace
is a Scribe-flavored action, not a plain operation. */
.btn-workspace {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.45rem 1rem;
background: var(--gradient-cta);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
box-shadow: var(--glow-cta);
transition: box-shadow 0.15s, opacity 0.15s;
}
.btn-workspace:hover { box-shadow: var(--glow-cta-hover); opacity: 0.95; color: var(--fs-text-on-action); } .btn-workspace:hover { box-shadow: var(--glow-cta-hover); opacity: 0.95; color: var(--fs-text-on-action); }
/* Share: Bronze action-secondary — alternate path */ /* Share: Bronze action-secondary — alternate path */
.btn-share {
padding: 0.4rem 0.8rem;
background: var(--color-action-secondary);
border: none;
color: var(--fs-text-on-action);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-share:hover { background: var(--color-action-secondary-hover); }
/* Delete project: Oxblood action-destructive ghost — outline form since
the actual confirm modal carries the filled destructive treatment */
.btn-danger-outline {
padding: 0.4rem 0.8rem;
background: none;
border: 1px solid var(--color-action-destructive);
color: var(--color-action-destructive);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
font-family: inherit;
transition: background 0.15s, color 0.15s;
}
.btn-danger-outline:hover { background: var(--color-action-destructive); color: var(--fs-text-on-action); }
.error-msg { color: var(--color-danger); font-size: 0.9rem; }
/* ── Project identity header ─────────────────────────────────── */
.project-header { margin-bottom: 1rem; }
.title-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.4rem;
}
.project-title-input { .project-title-input {
flex: 1; flex: 1;
font-size: 1.75rem; font-size: 1.75rem;
@@ -1062,30 +991,6 @@ async function confirmDelete() {
.edit-textarea { resize: vertical; } .edit-textarea { resize: vertical; }
/* Save panel: Moss action-primary per Hybrid rule */ /* Save panel: Moss action-primary per Hybrid rule */
.btn-save-panel {
padding: 0.45rem 0.9rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
font-family: inherit;
width: 100%;
transition: background 0.15s;
}
.btn-save-panel:hover:not(:disabled) { background: var(--color-action-primary-hover); }
.btn-save-panel:disabled { opacity: 0.45; cursor: default; }
/* ── Content area ────────────────────────────────────────────── */
.content-area { display: flex; flex-direction: column; gap: 0.75rem; }
.tab-bar {
display: flex;
gap: 0;
border-bottom: 1px solid var(--color-border);
}
.tab-btn { .tab-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -1148,49 +1053,6 @@ async function confirmDelete() {
} }
.milestone-title-input:focus { outline: none; border-color: var(--color-primary); } .milestone-title-input:focus { outline: none; border-color: var(--color-primary); }
/* Milestone confirm: Moss action-primary; Cancel: Bronze action-secondary */ /* Milestone confirm: Moss action-primary; Cancel: Bronze action-secondary */
.btn-ms-confirm {
padding: 0.3rem 0.65rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.78rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-ms-confirm:hover:not(:disabled) { background: var(--color-action-primary-hover); }
.btn-ms-confirm:disabled { opacity: 0.5; cursor: default; }
.btn-ms-cancel {
padding: 0.3rem 0.65rem;
background: var(--color-action-secondary);
border: none;
color: var(--fs-text-on-action);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.78rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-ms-cancel:hover { background: var(--color-action-secondary-hover); }
/* ── Milestone group ─────────────────────────────────────────── */
.milestone-group {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.milestone-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.55rem 0.85rem;
background: var(--color-bg-secondary);
font-size: 0.85rem;
user-select: none;
border-bottom: 1px solid var(--color-border);
}
.milestone-header.clickable { cursor: pointer; } .milestone-header.clickable { cursor: pointer; }
.milestone-header.clickable:hover { background: color-mix(in srgb, var(--color-primary) 4%, var(--color-bg-secondary)); } .milestone-header.clickable:hover { background: color-mix(in srgb, var(--color-primary) 4%, var(--color-bg-secondary)); }
+4 -68
View File
@@ -645,7 +645,7 @@ useEditorGuards(dirty, save);
@focus="onParentFocus" @focus="onParentFocus"
@blur="hideParentDropdown" @blur="hideParentDropdown"
/> />
<button v-if="parentId" class="btn-clear-parent" @click="clearParentTask" title="Clear">&times;</button> <button v-if="parentId" class="btn-text btn-clear-parent" @click="clearParentTask" title="Clear">&times;</button>
</div> </div>
<div v-if="showParentDropdown" class="parent-dropdown"> <div v-if="showParentDropdown" class="parent-dropdown">
<div v-if="parentSearchLoading" class="parent-dropdown-item parent-empty">Searching...</div> <div v-if="parentSearchLoading" class="parent-dropdown-item parent-empty">Searching...</div>
@@ -666,7 +666,7 @@ useEditorGuards(dirty, save);
<div v-if="isEditing" class="subtasks-section"> <div v-if="isEditing" class="subtasks-section">
<div class="subtasks-header"> <div class="subtasks-header">
<span class="subtasks-label">Sub-tasks</span> <span class="subtasks-label">Sub-tasks</span>
<button class="btn-add-subtask" @click="addingSubTask = !addingSubTask">+ Add</button> <button class="btn-text" @click="addingSubTask = !addingSubTask">+ Add</button>
</div> </div>
<div v-if="subTasksLoading" class="subtasks-loading">Loading...</div> <div v-if="subTasksLoading" class="subtasks-loading">Loading...</div>
<template v-else> <template v-else>
@@ -686,8 +686,8 @@ useEditorGuards(dirty, save);
@keydown.escape="addingSubTask = false; newSubTaskTitle = ''" @keydown.escape="addingSubTask = false; newSubTaskTitle = ''"
autofocus autofocus
/> />
<button class="btn-subtask-confirm" @click="createSubTask" :disabled="!newSubTaskTitle.trim()">Add</button> <button class="btn-primary btn-compact" @click="createSubTask" :disabled="!newSubTaskTitle.trim()">Add</button>
<button class="btn-subtask-cancel" @click="addingSubTask = false; newSubTaskTitle = ''">Cancel</button> <button class="btn-ghost btn-compact" @click="addingSubTask = false; newSubTaskTitle = ''">Cancel</button>
</div> </div>
</template> </template>
</div> </div>
@@ -921,23 +921,6 @@ useEditorGuards(dirty, save);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.04em; letter-spacing: 0.04em;
} }
.btn-add-subtask {
background: none;
border: none;
cursor: pointer;
color: var(--color-primary);
font-size: 0.78rem;
font-family: inherit;
padding: 0.1rem 0.2rem;
}
.btn-add-subtask:hover { opacity: 0.8; }
.subtasks-loading { font-size: 0.78rem; color: var(--color-text-muted); }
.subtask-row {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.2rem 0;
}
.subtask-checkbox { flex-shrink: 0; cursor: pointer; } .subtask-checkbox { flex-shrink: 0; cursor: pointer; }
.subtask-title { .subtask-title {
font-size: 0.83rem; font-size: 0.83rem;
@@ -968,33 +951,6 @@ useEditorGuards(dirty, save);
font-family: inherit; font-family: inherit;
} }
.subtask-input:focus { outline: none; border-color: var(--color-primary); } .subtask-input:focus { outline: none; border-color: var(--color-primary); }
.btn-subtask-confirm {
padding: 0.25rem 0.5rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.78rem;
font-family: inherit;
}
.btn-subtask-confirm:disabled { opacity: 0.5; cursor: default; }
.btn-subtask-cancel {
padding: 0.25rem 0.5rem;
background: none;
border: 1px solid var(--color-border);
color: var(--color-text-secondary);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.78rem;
font-family: inherit;
}
/* Streaming preview */
.stream-label {
font-size: 0.8rem;
color: var(--color-text-muted);
}
.stream-preview { .stream-preview {
border: 1px solid var(--color-input-border); border: 1px solid var(--color-input-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
@@ -1125,24 +1081,4 @@ useEditorGuards(dirty, save);
color: var(--color-primary, #6366f1); color: var(--color-primary, #6366f1);
font-style: normal; font-style: normal;
} }
.btn-reconsolidate {
margin-left: auto;
padding: 0.25rem 0.7rem;
font-size: 0.78rem;
font-style: normal;
color: inherit;
background: transparent;
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
border-radius: 999px;
cursor: pointer;
transition: background 120ms ease;
}
.btn-reconsolidate:hover:not(:disabled) {
background: rgba(99, 102, 241, 0.12);
border-color: var(--color-primary, #6366f1);
}
.btn-reconsolidate:disabled {
opacity: 0.5;
cursor: progress;
}
</style> </style>
+5 -82
View File
@@ -263,29 +263,29 @@ const subTaskProgress = computed(() => {
<div class="toolbar"> <div class="toolbar">
<router-link <router-link
:to="store.currentTask.project_id ? `/projects/${store.currentTask.project_id}` : '/tasks'" :to="store.currentTask.project_id ? `/projects/${store.currentTask.project_id}` : '/tasks'"
class="btn-back" class="btn-ghost"
>{{ store.currentTask.project_id ? "← Project" : "← Tasks" }}</router-link> >{{ store.currentTask.project_id ? "← Project" : "← Tasks" }}</router-link>
<router-link <router-link
:to="`/tasks/${store.currentTask.id}/edit`" :to="`/tasks/${store.currentTask.id}/edit`"
class="btn-edit" class="btn-primary"
> >
Edit Edit
</router-link> </router-link>
<button <button
v-if="advanceLabel" v-if="advanceLabel"
class="btn-advance" class="btn-primary"
@click="advanceStatus" @click="advanceStatus"
> >
{{ advanceLabel }} {{ advanceLabel }}
</button> </button>
<button <button
class="btn-convert" class="btn-secondary btn-compact"
@click="convertToNote" @click="convertToNote"
:disabled="converting" :disabled="converting"
> >
{{ converting ? "Converting..." : "Convert to Note" }} {{ converting ? "Converting..." : "Convert to Note" }}
</button> </button>
<button class="btn-share" @click="showShare = true">Share</button> <button class="btn-secondary btn-compact" @click="showShare = true">Share</button>
</div> </div>
<!-- Breadcrumb: parent task project milestone --> <!-- Breadcrumb: parent task project milestone -->
@@ -475,83 +475,6 @@ const subTaskProgress = computed(() => {
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.btn-back {
display: inline-flex;
align-items: center;
padding: 0.45rem 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: none;
color: var(--color-text-secondary);
text-decoration: none;
cursor: pointer;
font-size: 0.9rem;
}
.btn-back:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
/* Edit + Advance: Moss action-primary — both are "operating the software"
workflow actions, not brand moments. */
.btn-edit,
.btn-advance {
display: inline-flex;
align-items: center;
padding: 0.45rem 1.1rem;
border: none;
border-radius: var(--radius-sm);
background: var(--color-action-primary);
color: var(--fs-text-on-action);
text-decoration: none;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.15s;
}
.btn-edit:hover,
.btn-advance:hover {
background: var(--color-action-primary-hover);
color: var(--fs-text-on-action);
}
/* Convert + Share: Bronze action-secondary — alternate paths */
.btn-convert {
margin-left: auto;
padding: 0.3rem 0.75rem;
background: var(--color-action-secondary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.85rem;
transition: background 0.15s;
}
.btn-convert:hover { background: var(--color-action-secondary-hover); }
.btn-convert:disabled {
opacity: 0.6;
cursor: default;
}
.btn-share {
padding: 0.3rem 0.75rem;
background: var(--color-action-secondary);
border: none;
border-radius: var(--radius-sm);
color: var(--fs-text-on-action);
cursor: pointer;
font-size: 0.85rem;
font-family: inherit;
transition: background 0.15s;
}
.btn-share:hover { background: var(--color-action-secondary-hover); }
.task-title {
font-family: "Fraunces", Georgia, serif;
font-size: 2rem;
font-weight: 500;
line-height: 1.2;
margin: 0.25rem 0 0.5rem;
color: var(--color-text);
}
.meta { .meta {
display: flex; display: flex;
+5 -19
View File
@@ -25,7 +25,7 @@ onMounted(() => store.fetchTrash());
<h1>Trash</h1> <h1>Trash</h1>
<button <button
v-if="store.batches.length" v-if="store.batches.length"
class="btn-empty" class="btn-ghost btn-compact"
@click="empty" @click="empty"
>Empty trash</button> >Empty trash</button>
</header> </header>
@@ -52,8 +52,8 @@ onMounted(() => store.fetchTrash());
</div> </div>
</div> </div>
<div class="batch-actions"> <div class="batch-actions">
<button class="btn-restore" @click="store.restore(b.batch_id)">Restore</button> <button class="btn-ghost btn-compact btn-restore" @click="store.restore(b.batch_id)">Restore</button>
<button class="btn-purge" @click="purge(b.batch_id)">Delete permanently</button> <button class="btn-ghost btn-compact btn-purge" @click="purge(b.batch_id)">Delete permanently</button>
</div> </div>
</li> </li>
</ul> </ul>
@@ -64,25 +64,11 @@ onMounted(() => store.fetchTrash());
.trash-page { max-width: 900px; margin: 0 auto; padding: 1.5rem; } .trash-page { max-width: 900px; margin: 0 auto; padding: 1.5rem; }
.trash-header { display: flex; align-items: center; justify-content: space-between; } .trash-header { display: flex; align-items: center; justify-content: space-between; }
.trash-header h1 { font-family: Fraunces, serif; font-style: italic; margin: 0; } .trash-header h1 { font-family: Fraunces, serif; font-style: italic; margin: 0; }
.btn-empty {
background: none; border: 1px solid var(--color-border, #2a2a2e);
color: inherit; border-radius: 6px; padding: 0.4rem 0.8rem; cursor: pointer;
}
.btn-empty:hover { border-color: var(--color-danger, #ef4444); color: var(--color-danger, #ef4444); }
.trash-note { opacity: 0.7; font-size: 0.9em; margin: 0.5rem 0 1.5rem; }
.trash-loading, .trash-empty { opacity: 0.6; font-style: italic; padding: 2rem 0; }
.batch-list { list-style: none; padding: 0; margin: 0; }
.batch {
display: flex; align-items: center; justify-content: space-between;
gap: 1rem; padding: 0.85rem 1rem; margin-bottom: 0.5rem;
background: var(--color-surface, #18181b); border-radius: 8px;
border-left: 2px solid var(--color-border, #2a2a2e);
}
.batch-summary { font-weight: 500; } .batch-summary { font-weight: 500; }
.batch-count { opacity: 0.6; font-weight: 400; font-size: 0.9em; margin-left: 0.35rem; } .batch-count { opacity: 0.6; font-weight: 400; font-size: 0.9em; margin-left: 0.35rem; }
.batch-meta { font-size: 0.82em; opacity: 0.6; margin-top: 0.25rem; } .batch-meta { font-size: 0.82em; opacity: 0.6; margin-top: 0.25rem; }
.batch-actions { display: flex; gap: 0.5rem; flex-shrink: 0; } .batch-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }
.batch-actions button { border-radius: 6px; padding: 0.35rem 0.7rem; cursor: pointer; border: 1px solid var(--color-border, #2a2a2e); background: none; color: inherit; } .batch-actions button { border-radius: 6px; padding: 0.35rem 0.7rem; cursor: pointer; border: 1px solid var(--color-border, #2a2a2e); background: none; color: inherit; }
.btn-restore:hover { border-color: var(--color-primary, #6366f1); color: var(--color-primary, #6366f1); } .btn-restore:hover { border-color: var(--color-action-primary); color: var(--color-action-primary); }
.btn-purge:hover { border-color: var(--color-danger, #ef4444); color: var(--color-danger, #ef4444); } .btn-purge:hover { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
</style> </style>