CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 25s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 43s
#2277 counted ~150 "raw colour literals bypassing the tokens". Measuring them told a different story: 184 sat in `var(--token, #fallback)` position, and a check against theme.css shows every one of those tokens IS declared. So the fallbacks could not render. Not drift — vestigial. They were also not this palette. The most common were Tailwind and Flat-UI defaults — #6366f1 indigo, #22c55e green, #f59e0b amber, #3b82f6 blue, #e74c3c and #27ae60 — a second, unsanctioned colour scheme sitting in the codebase looking like the app's colours to anyone reading it. Removing them is not tidying. #2319's lesson is that a fallback is WORSE than a missing token: a missing token renders as nothing and someone eventually notices, while a fallback renders something plausible forever. These 184 were one token rename away from silently repainting the app in Tailwind. The design token check would catch the rename — but the fallback is precisely the thing that would make it invisible if the check were ever bypassed. Literal count 152 -> 45, which matters beyond the number: a report that is mostly unreachable noise is one people stop reading, and then it stops working while still passing. What remains should be genuinely worth looking at. Done with a paren-aware transform, not a regex — `var(--x, rgba(0,0,0,.5))` nests parens and `[^)]+` would cut at the first one and leave `))` behind. Verified after: every changed line is a fallback strip and nothing else, and every var() reference still resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
75 lines
2.9 KiB
Vue
75 lines
2.9 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted } from "vue";
|
|
import { useTrashStore } from "@/stores/trash";
|
|
import { relativeTime } from "@/composables/useRelativeTime";
|
|
|
|
const store = useTrashStore();
|
|
|
|
async function purge(batchId: string) {
|
|
if (!confirm("Permanently delete this? This cannot be undone.")) return;
|
|
await store.purge(batchId);
|
|
}
|
|
|
|
async function empty() {
|
|
if (!store.batches.length) return;
|
|
if (!confirm("Permanently delete everything in the trash? This cannot be undone.")) return;
|
|
await store.emptyTrash();
|
|
}
|
|
|
|
onMounted(() => store.fetchTrash());
|
|
</script>
|
|
|
|
<template>
|
|
<main class="trash-page">
|
|
<header class="trash-header">
|
|
<h1>Trash</h1>
|
|
<button
|
|
v-if="store.batches.length"
|
|
class="btn-ghost btn-compact"
|
|
@click="empty"
|
|
>Empty trash</button>
|
|
</header>
|
|
|
|
<p class="trash-note">
|
|
Deleted items are kept here and can be restored. They're permanently removed
|
|
after the retention window set in Settings.
|
|
</p>
|
|
|
|
<div v-if="store.loading" class="trash-loading">Loading…</div>
|
|
|
|
<p v-else-if="!store.batches.length" class="trash-empty">Trash is empty.</p>
|
|
|
|
<ul v-else class="batch-list">
|
|
<li v-for="b in store.batches" :key="b.batch_id" class="batch">
|
|
<div class="batch-main">
|
|
<div class="batch-summary">
|
|
{{ b.summary }}
|
|
<span v-if="b.count > 1" class="batch-count">+ {{ b.count - 1 }} item{{ b.count - 1 === 1 ? '' : 's' }}</span>
|
|
</div>
|
|
<div class="batch-meta">
|
|
deleted {{ b.deleted_at ? relativeTime(b.deleted_at) : '' }}
|
|
<span class="batch-types">· {{ b.items.map(i => i.type).join(', ') }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="batch-actions">
|
|
<button class="btn-ghost btn-compact btn-restore" @click="store.restore(b.batch_id)">Restore</button>
|
|
<button class="btn-ghost btn-compact btn-purge" @click="purge(b.batch_id)">Delete permanently</button>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</main>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.trash-page { max-width: 900px; margin: 0 auto; padding: 1.5rem; }
|
|
.trash-header { display: flex; align-items: center; justify-content: space-between; }
|
|
.trash-header h1 { font-family: Fraunces, serif; font-style: italic; margin: 0; }
|
|
.batch-summary { font-weight: 500; }
|
|
.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-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); background: none; color: inherit; }
|
|
.btn-restore:hover { border-color: var(--color-action-primary); color: var(--color-action-primary); }
|
|
.btn-purge:hover { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
|
|
</style>
|