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
256 lines
5.8 KiB
Vue
256 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { getSharedWithMe } from '@/api/client'
|
|
|
|
interface SharedProject {
|
|
id: number
|
|
title: string
|
|
description: string | null
|
|
status: string
|
|
color: string | null
|
|
permission: string
|
|
owner_username: string
|
|
}
|
|
|
|
interface SharedNote {
|
|
id: number
|
|
title: string
|
|
is_task: boolean
|
|
permission: string
|
|
owner_username: string
|
|
}
|
|
|
|
const projects = ref<SharedProject[]>([])
|
|
const notes = ref<SharedNote[]>([])
|
|
const loading = ref(true)
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const data = await getSharedWithMe()
|
|
projects.value = data.projects as unknown as SharedProject[]
|
|
notes.value = data.notes as unknown as SharedNote[]
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="shared-page page-container">
|
|
<header class="page-header">
|
|
<h1 class="page-title">Shared with me</h1>
|
|
</header>
|
|
|
|
<div v-if="loading" class="loading-state">Loading…</div>
|
|
|
|
<template v-else>
|
|
<!-- Projects -->
|
|
<section class="shared-section">
|
|
<h2 class="section-heading">Projects</h2>
|
|
<div v-if="projects.length" class="shared-grid">
|
|
<router-link
|
|
v-for="p in projects"
|
|
:key="p.id"
|
|
:to="`/projects/${p.id}`"
|
|
class="shared-card"
|
|
>
|
|
<div class="card-color-bar" :style="p.color ? `background: ${p.color}` : ''" />
|
|
<div class="card-body">
|
|
<div class="card-title">{{ p.title }}</div>
|
|
<div class="card-meta">
|
|
<span class="card-owner">by {{ p.owner_username }}</span>
|
|
<span class="perm-badge" :class="`perm-${p.permission}`">{{ p.permission }}</span>
|
|
</div>
|
|
<p v-if="p.description" class="card-desc">{{ p.description }}</p>
|
|
</div>
|
|
</router-link>
|
|
</div>
|
|
<p v-else class="empty-msg">No projects shared with you yet.</p>
|
|
</section>
|
|
|
|
<!-- Notes & Tasks -->
|
|
<section class="shared-section">
|
|
<h2 class="section-heading">Notes & Tasks</h2>
|
|
<div v-if="notes.length" class="shared-list">
|
|
<router-link
|
|
v-for="n in notes"
|
|
:key="n.id"
|
|
:to="n.is_task ? `/tasks/${n.id}` : `/notes/${n.id}`"
|
|
class="shared-row"
|
|
>
|
|
<span class="row-icon">{{ n.is_task ? '✓' : '📄' }}</span>
|
|
<span class="row-title">{{ n.title || '(untitled)' }}</span>
|
|
<span class="row-owner">by {{ n.owner_username }}</span>
|
|
<span class="perm-badge" :class="`perm-${n.permission}`">{{ n.permission }}</span>
|
|
</router-link>
|
|
</div>
|
|
<p v-else class="empty-msg">No notes or tasks shared with you yet.</p>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.shared-page {
|
|
padding: 2rem;
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.page-title {
|
|
font-family: 'Fraunces', Georgia, serif;
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.loading-state {
|
|
color: var(--color-muted);
|
|
padding: 2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.shared-section {
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
|
|
.section-heading {
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.07em;
|
|
color: var(--color-muted);
|
|
margin: 0 0 0.75rem;
|
|
}
|
|
|
|
.shared-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.shared-card {
|
|
display: flex;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
text-decoration: none;
|
|
transition: box-shadow 0.15s, transform 0.15s;
|
|
}
|
|
.shared-card:hover {
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.card-color-bar {
|
|
width: 4px;
|
|
flex-shrink: 0;
|
|
background: var(--color-border);
|
|
}
|
|
|
|
.card-body {
|
|
padding: 0.9rem 1rem;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.card-title {
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
color: var(--color-text);
|
|
margin-bottom: 0.3rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.card-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
.card-owner {
|
|
font-size: 0.78rem;
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.card-desc {
|
|
font-size: 0.82rem;
|
|
color: var(--color-muted);
|
|
margin: 0;
|
|
overflow: hidden;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
|
|
.shared-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.shared-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
padding: 0.6rem 0.9rem;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
text-decoration: none;
|
|
transition: background 0.1s;
|
|
}
|
|
.shared-row:hover {
|
|
background: var(--color-hover);
|
|
}
|
|
|
|
.row-icon {
|
|
font-size: 0.9rem;
|
|
flex-shrink: 0;
|
|
color: var(--color-muted);
|
|
}
|
|
.row-title {
|
|
flex: 1;
|
|
font-size: 0.9rem;
|
|
color: var(--color-text);
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.row-owner {
|
|
font-size: 0.78rem;
|
|
color: var(--color-muted);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.perm-badge {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
padding: 0.15rem 0.45rem;
|
|
border-radius: 4px;
|
|
white-space: nowrap;
|
|
}
|
|
.perm-viewer { background: color-mix(in srgb, var(--color-muted) 15%, transparent); color: var(--color-muted); }
|
|
.perm-editor { background: color-mix(in srgb, var(--color-primary) 15%, transparent); color: var(--color-primary); }
|
|
.perm-admin { background: color-mix(in srgb, var(--color-warning) 15%, transparent); color: var(--color-warning); }
|
|
|
|
.empty-msg {
|
|
color: var(--color-muted);
|
|
font-size: 0.88rem;
|
|
margin: 0;
|
|
padding: 1rem 0;
|
|
}
|
|
</style>
|