Buttons: one definition, aligned to the design system — plus local prior-art recall #94

Merged
bvandeusen merged 12 commits from dev into main 2026-08-02 18:50:04 -04:00
12 changed files with 45 additions and 638 deletions
Showing only changes of commit 5f8b824523 - Show all commits
+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>