From f11a547cd2355a08a811b929f9462833c475172b Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 8 Aug 2026 19:58:01 -0400 Subject: [PATCH 1/5] fix(lists): shared-project records appear, and a list's q means what search means MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2462, both decided halves. ## The ACL defect list_notes filtered on Note.user_id == user_id with no scope parameter at all — never a per-call decision, the capability was absent. query_knowledge beside it was deliberately browse-scoped with a comment saying why. So a task in a shared project was invisible in list_tasks, enter_project's open-task list, the SessionStart todo count and the web UI's task views, while the same project's notes and snippets appeared. Now the shared clause: notes_visibility_clause(user_id, "browse"). Browse and not read, per decision #2094 — an ambient list must never surface a record someone shared one-to-one; those stay search-only. This is the remaining half of a fix made twice before (#2159 widened fetch, #2092 widened meaning), and the guard below is what stops a fourth half appearing. Guarded by source inspection in test_retrieval_scopes: every list-shaped service references a shared visibility clause AND carries no bare Note.user_id comparison that would quietly re-narrow it. An owner-only list returns correct-looking rows and simply omits the shared ones — the shape no behavioural test catches. ## q is semantic (operator: "make it match") The UI's note list keyword-matched while the UI's Browse search semantic-matched, over the same records, with nothing saying so. Now one meaning: q joins the embedding index and orders by cosine distance at the interactive floor, with every lifecycle filter still applied in the same indexed query. Relevance ordering wins over `sort` when q is present — a query is a relevance claim, and sorting its results by date would shuffle the answer. ILIKE survives only as the embedder-down fallback: degraded, never empty. Stated position: superseded records are NOT demoted in list-q. The penalty reorders a top-k; reordering a paginated, counted list would make page boundaries lie. The search surfaces carry the demotion. ## The interactive floor becomes one constant INTERACTIVE_SEARCH_THRESHOLD = 0.3 in embeddings.py, consumed by routes/search.py (was a commented constant), knowledge.py (was a bare literal), and the new list-q. This closes #2463's finding 3 early — the same number lived in two files with the reasoning attached to only one. ## Deferred, deliberately The return-shape unification (ORM objects vs dicts) stays undone. It is a 14-caller refactor whose motivation — callers being unable to swap paths — shrinks now that both paths share the clause and the meaning of q. If swap pressure recurs it deserves its own change, not a rider on an ACL fix. Refs #2462, #2463 --- src/scribe/mcp/tools/notes.py | 11 +++-- src/scribe/routes/search.py | 8 ++-- src/scribe/services/embeddings.py | 9 ++++ src/scribe/services/knowledge.py | 10 ++++- src/scribe/services/notes.py | 72 +++++++++++++++++++++++++------ tests/test_retrieval_scopes.py | 43 ++++++++++++++++++ 6 files changed, 132 insertions(+), 21 deletions(-) diff --git a/src/scribe/mcp/tools/notes.py b/src/scribe/mcp/tools/notes.py index c9a0aa8..f1de8b5 100644 --- a/src/scribe/mcp/tools/notes.py +++ b/src/scribe/mcp/tools/notes.py @@ -32,10 +32,15 @@ async def list_notes( ) -> dict: """List notes (non-task documents) stored in Scribe. - Optionally filter by a single tag (plain string, no # prefix) or a keyword - search against title and body. Results are ordered by last-updated descending. + Optionally filter by a single tag (plain string, no # prefix) or by + `search_text`, which matches on MEANING — the same semantic match every + search surface uses (#2462; keyword ILIKE is only the fallback when the + embedder is down). With `search_text`, results order by relevance; without + it, by last-updated descending. Reaches your records plus shared-project + records, like every other list. - Use search for semantic/meaning-based lookup instead of exact keyword search. + Prefer `search` for a pure lookup — it returns scores and reaches + everything readable; this adds the lifecycle filters on top. Args: project_id: Scope to one project. PASS THE ACTIVE PROJECT'S ID whenever a diff --git a/src/scribe/routes/search.py b/src/scribe/routes/search.py index bdfe6b5..cba492c 100644 --- a/src/scribe/routes/search.py +++ b/src/scribe/routes/search.py @@ -4,12 +4,14 @@ from quart import Blueprint, jsonify, request from scribe.auth import login_required, get_current_user_id from scribe.services.access import owner_names_for +from scribe.services.embeddings import ( + INTERACTIVE_SEARCH_THRESHOLD as _REST_SEARCH_THRESHOLD, +) from scribe.services.embeddings import semantic_search_notes from scribe.services.retrieval_telemetry import record_retrieval -# This route searches with a looser floor than the MCP tool default — it powers -# an interactive feed where loosely-related hits still have value. -_REST_SEARCH_THRESHOLD = 0.3 +# The interactive floor lives in embeddings.py now, shared with Browse search +# and the list views' semantic `q` — one number, one rationale (#2463). search_bp = Blueprint("search", __name__, url_prefix="/api/search") diff --git a/src/scribe/services/embeddings.py b/src/scribe/services/embeddings.py index 13b576a..21e1483 100644 --- a/src/scribe/services/embeddings.py +++ b/src/scribe/services/embeddings.py @@ -31,6 +31,15 @@ logger = logging.getLogger(__name__) # loosely-related results that pad the sidebar without adding real value. _SIMILARITY_THRESHOLD = 0.45 +# The floor for INTERACTIVE, human-facing feeds — REST /api/search, Browse +# search, and the list views' semantic `q`. Deliberately looser than the agent +# default above: a human scanning a result list gets value from loosely-related +# hits an agent would be misled by. One constant, because this number was +# written as `0.3` in two files with the reasoning attached to only one of +# them — the exact shape where a later tuner moves one and not the other +# (#2463 finding 3). +INTERACTIVE_SEARCH_THRESHOLD = 0.3 + # Public alias so callers (and telemetry) can record the effective default # threshold without reaching for the underscored name. DEFAULT_SIMILARITY_THRESHOLD = _SIMILARITY_THRESHOLD diff --git a/src/scribe/services/knowledge.py b/src/scribe/services/knowledge.py index f62cbba..dea6bbc 100644 --- a/src/scribe/services/knowledge.py +++ b/src/scribe/services/knowledge.py @@ -396,14 +396,20 @@ async def _semantic_knowledge_search( # case a semantic search exists to serve. semantic_notes: list[Note] = [] try: - from scribe.services.embeddings import semantic_search_notes + from scribe.services.embeddings import ( + INTERACTIVE_SEARCH_THRESHOLD, + semantic_search_notes, + ) is_task_filter = True if note_type in ("task", "plan") else (False if note_type else None) candidates = await semantic_search_notes( user_id=user_id, scope="read", query=q, limit=min(200, limit * 4), - threshold=0.3, + # The shared interactive floor — this was a bare `0.3` while + # routes/search.py had the same number as a commented constant, the + # exact pair where one moves and the other doesn't (#2463). + threshold=INTERACTIVE_SEARCH_THRESHOLD, is_task=is_task_filter, project_id=project_id, ) diff --git a/src/scribe/services/notes.py b/src/scribe/services/notes.py index 649a28d..ee9b130 100644 --- a/src/scribe/services/notes.py +++ b/src/scribe/services/notes.py @@ -188,10 +188,32 @@ async def list_notes( limit: int = 50, offset: int = 0, ) -> tuple[list[Note], int]: + """Lifecycle-shaped listing. Two contracts worth knowing: + + VISIBILITY is the shared browse clause (#47, #2462): the caller's own + records plus anything in a project they can reach — the same reach + query_knowledge has always had. Before this, list_notes was silently + owner-only, so a task in a shared project was invisible in list_tasks, + enter_project's open-task list and the web UI while the same project's + notes appeared. Browse, not read, per decision #2094: an ambient list must + never surface a record someone shared one-to-one — those stay search-only. + + `q` is SEMANTIC (operator decision, 2026-08-06: "make it match") — the same + meaning-based match as Browse search, at the interactive floor. When `q` is + present, relevance ordering wins and `sort` is ignored; a query is a + relevance claim and sorting its results by date would shuffle the answer. + Falls back to ILIKE substring match only when the embedder is unavailable — + degraded but never empty. Superseded records are not demoted here: the + penalty reorders a top-k, and reordering a paginated, counted list would + make page boundaries lie. The search surfaces carry the demotion. + """ + from scribe.services.access import notes_visibility_clause + + visible = notes_visibility_clause(user_id, "browse") async with async_session() as session: - query = select(Note).where(Note.user_id == user_id, Note.deleted_at.is_(None)) + query = select(Note).where(visible, Note.deleted_at.is_(None)) count_query = select(func.count(Note.id)).where( - Note.user_id == user_id, Note.deleted_at.is_(None) + visible, Note.deleted_at.is_(None) ) # Filter by task vs note @@ -202,14 +224,34 @@ async def list_notes( query = query.where(Note.status.is_(None)) count_query = count_query.where(Note.status.is_(None)) + semantic_order = None if q: - terms = _strip_type_nouns(q) - for term in terms: - escaped_term = term.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") - pattern = f"%{escaped_term}%" - term_filter = or_(Note.title.ilike(pattern), Note.body.ilike(pattern)) - query = query.where(term_filter) - count_query = count_query.where(term_filter) + query_vec = None + try: + from scribe.services.embeddings import get_embedding + query_vec = await get_embedding(q) + except Exception: + query_vec = None # embedder down → keyword fallback below + if query_vec is not None: + from scribe.models.embedding import NoteEmbedding + from scribe.services.embeddings import INTERACTIVE_SEARCH_THRESHOLD + distance = NoteEmbedding.embedding.cosine_distance(query_vec) + sem_filter = distance <= (1.0 - INTERACTIVE_SEARCH_THRESHOLD) + query = query.join( + NoteEmbedding, NoteEmbedding.note_id == Note.id + ).where(sem_filter) + count_query = count_query.join( + NoteEmbedding, NoteEmbedding.note_id == Note.id + ).where(sem_filter) + semantic_order = distance.asc() + else: + terms = _strip_type_nouns(q) + for term in terms: + escaped_term = term.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") + pattern = f"%{escaped_term}%" + term_filter = or_(Note.title.ilike(pattern), Note.body.ilike(pattern)) + query = query.where(term_filter) + count_query = count_query.where(term_filter) if tags: for i, tag in enumerate(tags): @@ -275,11 +317,15 @@ async def list_notes( query = query.where(paused_filter) count_query = count_query.where(paused_filter) - sort_col = getattr(Note, sort, Note.updated_at) - if order == "asc": - query = query.order_by(sort_col.asc()) + if semantic_order is not None: + # A query is a relevance claim — see the docstring. + query = query.order_by(semantic_order) else: - query = query.order_by(sort_col.desc()) + sort_col = getattr(Note, sort, Note.updated_at) + if order == "asc": + query = query.order_by(sort_col.asc()) + else: + query = query.order_by(sort_col.desc()) query = query.limit(limit).offset(offset) diff --git a/tests/test_retrieval_scopes.py b/tests/test_retrieval_scopes.py index 3f4fa6b..5e11394 100644 --- a/tests/test_retrieval_scopes.py +++ b/tests/test_retrieval_scopes.py @@ -202,3 +202,46 @@ async def test_injected_menu_labels_the_record_kind(): assert "body" not in out["context"] # The header can't claim they're all notes when the markers say otherwise. assert "Scribe records" in lines[0] + + +def test_list_shaped_services_use_the_shared_visibility_clause(): + """Every service that LISTS notes must declare its reach through + notes_visibility_clause / browsable_notes_clause — never a bare owner + filter (#47, #2462). + + Source inspection, because this is the shape no behavioural test catches: + an owner-only list returns correct-looking rows and simply omits the shared + ones, which is how list_notes shipped owner-only for months while + query_knowledge beside it was deliberately browse-scoped. A task in a + shared project was invisible in list_tasks, enter_project and the web UI + while the same project's notes appeared. + """ + import ast + import inspect + import textwrap + + from scribe.services import knowledge, notes + + for fn, label in ((notes.list_notes, "notes.list_notes"), + (knowledge.query_knowledge, "knowledge.query_knowledge")): + source = textwrap.dedent(inspect.getsource(fn)) + assert ( + "notes_visibility_clause" in source + or "browsable_notes_clause" in source + ), ( + f"{label} does not reference a shared visibility clause — a bare " + f"owner filter silently hides shared-project records (#2462)" + ) + # And it must not ALSO carry a bare owner filter on the main query, + # which would quietly re-narrow whatever the clause granted. + tree = ast.parse(source) + for node in ast.walk(tree): + if (isinstance(node, ast.Compare) + and isinstance(node.left, ast.Attribute) + and node.left.attr == "user_id" + and isinstance(node.left.value, ast.Name) + and node.left.value.id == "Note"): + raise AssertionError( + f"{label} compares Note.user_id directly — reach must come " + f"from the shared clause, not a bare owner filter" + ) From 45ba4aab254fb446838e846a47d5a90632dc584b Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 8 Aug 2026 20:00:22 -0400 Subject: [PATCH 2/5] fix(telemetry): the two invisible retrievals log, and /api/search takes scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2463, the remaining findings (finding 3 landed with f11a547). ## The reuse slot logs (source: reuse_slot) A real semantic query competing for an auto-inject menu slot, and the ledger was asymmetric: the scored hit it DISPLACED was in retrieval_logs, the query that displaced it was not — so the slot could never be evaluated against what it replaced. #1038 and #2085 are gated on this ledger being complete. ## Browse search logs (source: browse_search) The human's main search surface, and it logged nothing — so retrieval_logs claimed the web's search was /api/search. Measured while fixing: /api/search has ZERO frontend consumers; the web UI searches through /api/knowledge exclusively. The table wasn't just under-describing the UI, it was describing a surface the UI never touches. The task's check — does folding human queries into the corpus skew the precision signal thresholds are tuned against? — is answered by the source column: distinct values (browse_search, reuse_slot beside the existing four) mean tuning includes or excludes human traffic deliberately rather than by accident. Same resolution as the mcp_/rest_ split in note_usage_events. ## /api/search takes project_id (and logs it) The route logged project_id=None unconditionally while the MCP tool insists the agent pass the active project. Now optional, default global: with no frontend consumer, this route serves API callers, and an API caller states its scope explicitly — the default-scope UI decision the task flagged is moot until a UI actually consumes the route, which is recorded rather than guessed. Refs #2463 --- src/scribe/routes/search.py | 12 +++++++----- src/scribe/services/knowledge.py | 14 ++++++++++++++ src/scribe/services/plugin_context.py | 12 ++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/scribe/routes/search.py b/src/scribe/routes/search.py index cba492c..2cc891c 100644 --- a/src/scribe/routes/search.py +++ b/src/scribe/routes/search.py @@ -36,22 +36,24 @@ async def search_route(): content_type = request.args.get("content_type", "all") limit = min(request.args.get("limit", 10, type=int), 50) is_task = _content_type_to_is_task(content_type) - # Same association filter the MCP tool takes (#33). The project filter this - # route is still missing is #2463's — it carries a default-scope UI decision - # this change must not preempt. + # Same association filters the MCP tool takes (#33). Optional, default + # global: this route has NO frontend consumer today (measured 2026-08-08 — + # the web UI searches through /api/knowledge), so it serves API callers, + # and an API caller states its scope explicitly. system_id = request.args.get("system_id", type=int) + project_id = request.args.get("project_id", type=int) t0 = time.perf_counter() results = await semantic_search_notes( uid, q, limit=limit, is_task=is_task, threshold=_REST_SEARCH_THRESHOLD, - system_id=system_id, + project_id=project_id, system_id=system_id, # The user typed this, so it reaches everything they may read. scope="read", ) record_retrieval( user_id=uid, source="rest_search", query=q, threshold=_REST_SEARCH_THRESHOLD, limit=limit, - project_id=None, is_task=is_task, results=results, + project_id=project_id, is_task=is_task, results=results, duration_ms=(time.perf_counter() - t0) * 1000.0, ) owners = await owner_names_for( diff --git a/src/scribe/services/knowledge.py b/src/scribe/services/knowledge.py index dea6bbc..1d5517e 100644 --- a/src/scribe/services/knowledge.py +++ b/src/scribe/services/knowledge.py @@ -401,6 +401,8 @@ async def _semantic_knowledge_search( semantic_search_notes, ) is_task_filter = True if note_type in ("task", "plan") else (False if note_type else None) + import time as _time + _t0 = _time.perf_counter() candidates = await semantic_search_notes( user_id=user_id, scope="read", @@ -413,6 +415,18 @@ async def _semantic_knowledge_search( is_task=is_task_filter, project_id=project_id, ) + # The human's MAIN search surface, and it logged nothing — so + # retrieval_logs claimed the web's search was /api/search, a narrower + # path with (measured) zero frontend consumers. Distinct source, so + # threshold tuning can include or exclude human queries deliberately + # rather than by accident (#2463). + from scribe.services.retrieval_telemetry import record_retrieval + record_retrieval( + user_id=user_id, source="browse_search", query=q, + threshold=INTERACTIVE_SEARCH_THRESHOLD, limit=min(200, limit * 4), + project_id=project_id, is_task=is_task_filter, results=candidates, + duration_ms=(_time.perf_counter() - _t0) * 1000.0, + ) for _score, note in candidates: if note.deleted_at is not None: continue diff --git a/src/scribe/services/plugin_context.py b/src/scribe/services/plugin_context.py index 39a8537..4e5ce51 100644 --- a/src/scribe/services/plugin_context.py +++ b/src/scribe/services/plugin_context.py @@ -326,6 +326,7 @@ async def _reserve_slot_for_reuse( return kept # reuse already represented; nothing to do top_k = cfg["top_k"] + _t0 = time.perf_counter() reuse = await semantic_search_notes( user_id, query, limit=1, @@ -335,6 +336,17 @@ async def _reserve_slot_for_reuse( note_type=_REUSE_KINDS, scope="browse", ) + # A real semantic query competing for a menu slot — logged like the scored + # arm it displaces. Before this, the hit it PUSHED OUT was in + # retrieval_logs and the query that pushed it out was not, so the slot + # could never be evaluated against what it replaced (#2463; #1038 and + # #2085 are gated on this ledger being complete). + record_retrieval( + user_id=user_id, source="reuse_slot", query=query, + threshold=cfg["threshold"], limit=1, project_id=project_id, + is_task=None, results=reuse, + duration_ms=(time.perf_counter() - _t0) * 1000.0, + ) # Verify the kind rather than trusting the query that asked for it, and # dedup on top of exclude_ids. This slot exists FOR reuse kinds — a slot # silently spent on something else is worse than no slot, because the line From 9b3874b65753c9a1d3c6da27752b50d8839bff6b Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 8 Aug 2026 22:32:58 -0400 Subject: [PATCH 3/5] test: the auto-inject path now logs two retrievals, and that is the point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 45ba4aa made the reuse slot log its query (source: reuse_slot). The margin-gate test pinned record_retrieval to exactly one call, which was asserting the very asymmetry #2463 fixed — the displaced hit logged, the displacing query not. Assert both sources in order instead. Refs #2463 --- tests/test_services_plugin_context.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_services_plugin_context.py b/tests/test_services_plugin_context.py index e45c98e..7c96f96 100644 --- a/tests/test_services_plugin_context.py +++ b/tests/test_services_plugin_context.py @@ -104,9 +104,12 @@ async def test_build_autoinject_hint_titles_only_with_margin_gate(): assert "#33" not in out["context"] # Title-first: no body text, ever. assert "get_note(id)" in out["context"] - # Telemetry fired with the auto_inject source and the full candidate set. - rec.assert_called_once() - assert rec.call_args.kwargs["source"] == "auto_inject" + # Telemetry fired for BOTH retrievals this path runs: the scored menu and + # the reuse-slot query competing against it. The slot's query used to be + # the one unlogged retrieval on this path — the hit it displaced was in + # retrieval_logs, the query that displaced it was not (#2463). + sources = [c.kwargs["source"] for c in rec.call_args_list] + assert sources == ["auto_inject", "reuse_slot"] @pytest.mark.asyncio From 2d1e26f38fb6d38348b610c225b836af928b4fe2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 8 Aug 2026 22:39:13 -0400 Subject: [PATCH 4/5] =?UTF-8?q?feat(telemetry):=20ambient=20surfacings=20c?= =?UTF-8?q?ount,=20apart=20=E2=80=94=20enter=5Fproject=20and=20the=20skill?= =?UTF-8?q?=20sync=20emit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2477, option (a) as decided, with the readout changed in the same commit. ## The two silent surfaces enter_project returns open tasks + recent notes on every project entry — probably the largest surfacing by volume — and emitted nothing, so the pulls it caused floated unattributed and the surfaced:pulled ratio ran against a denominator missing its biggest contributor. Now source "enter_project". build_process_manifest installs every reachable Process as an auto-surfacing skill on the operator's machine — its own docstring calls it the most consequential passive surface Scribe has — and emitted nothing, so a Process matched on every relevant turn and never opened was indistinguishable from one never installed. Now source "process_skill_sync": the honest event is "installed", which is a surfacing in effect since the description sits in front of the model each session. ## The readout, same commit — the condition option (a) carried Both surfaces are AMBIENT: top-N-by-recency and install-everything are not ranked choices. Pooling them into surfaced_count would make a note's number dominated by "recently updated in a project you opened", and dead-weight detection would read that as popularity — the wrong number read confidently, which is the corrupts-data tier the survey ranked above everything else. So usage_for_notes splits: surfaced_count stays RANKED-ONLY (every existing consumer's reading — "surfaced often, never pulled → dead weight" — keeps meaning what it meant), and ambient_count is new. Classified in SQL via a CASE on AMBIENT_SOURCES so the group count stays three rows per note, not one per distinct source. Pulls stay pooled: "did anyone ever open this?" does not depend on how it was found. #1038 and #2085 read agent pulls and ranked surfacings; both are unaffected by ambient volume, which is the point. Refs #2477 --- src/scribe/mcp/tools/projects.py | 13 +++++++ src/scribe/services/note_usage.py | 49 +++++++++++++++++++++++---- src/scribe/services/plugin_context.py | 13 +++++++ tests/test_note_usage.py | 12 ++++++- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/scribe/mcp/tools/projects.py b/src/scribe/mcp/tools/projects.py index 5e4ee07..c8bfddc 100644 --- a/src/scribe/mcp/tools/projects.py +++ b/src/scribe/mcp/tools/projects.py @@ -24,6 +24,7 @@ from scribe.services import projects as projects_svc from scribe.services import rulebooks as rulebooks_svc from scribe.services import systems as systems_svc from scribe.services import trash as trash_svc +from scribe.services.note_usage import record_surfaced async def list_projects() -> dict: @@ -94,6 +95,18 @@ async def enter_project(project_id: int) -> dict: # agent when it writes — which it never was, and tagging stopped within # three days of the feature landing (#2546's audit). systems = await systems_svc.list_systems(uid, project_id) + + # Probably the largest surfacing by volume, and it emitted nothing — so + # the pulls it caused floated unattributed and the surfaced:pulled ratio + # ran against a denominator missing its biggest contributor (#2477). An + # AMBIENT source: these are top-N-by-recency, not a ranked choice, and the + # readout counts them apart so dead-weight detection isn't poisoned by + # "recently updated in a project you opened". + record_surfaced( + user_id=uid, + note_ids=[int(t.id) for t in open_tasks] + [int(n.id) for n in recent_notes], + source="enter_project", + ) # A project need not have one, and most installs won't — null is ordinary # here, not a missing prerequisite. design_system = None diff --git a/src/scribe/services/note_usage.py b/src/scribe/services/note_usage.py index a268020..041b42a 100644 --- a/src/scribe/services/note_usage.py +++ b/src/scribe/services/note_usage.py @@ -26,7 +26,7 @@ from __future__ import annotations import asyncio import logging -from sqlalchemy import func, select +from sqlalchemy import case, func, select from scribe.models import async_session from scribe.models.note_usage import PULLED, SURFACED, NoteUsageEvent @@ -96,14 +96,30 @@ def record_pulled(*, user_id: int | None, note_id: int, source: str) -> None: _schedule(rows) +# Surfacings that are NOT a ranked choice. enter_project returns whatever the +# top-N-by-recency happen to be; the skill sync installs every Process the +# operator can reach. Counting those alongside auto-inject would make a note's +# surfaced_count dominated by "it was recently updated in a project you +# opened", and dead-weight detection would read that as popularity (#2477). +# They still matter — a pull that follows one must not float unattributed — so +# they land in their own bucket rather than not landing at all. +AMBIENT_SOURCES = ("enter_project", "process_skill_sync") + + def empty_usage() -> dict: """The zero readout — what a note with no recorded events looks like. Callers render this shape unconditionally, so a note predating the table reads as "never surfaced, never pulled" rather than as a missing key. + + `surfaced_count` is RANKED surfacings only — a scored surface chose this + record. `ambient_count` is the rest (see AMBIENT_SOURCES). The split is the + readout half of #2477: the "high surfaced, zero pulls → dead weight" + reading is only valid over surfacings that were choices. """ return { "surfaced_count": 0, + "ambient_count": 0, "pull_count": 0, "last_surfaced_at": None, "last_pulled_at": None, @@ -132,9 +148,23 @@ async def usage_for_notes(note_ids: list[int]) -> dict[int, dict]: NoteUsageEvent.event, func.count().label("n"), func.max(NoteUsageEvent.created_at).label("last_at"), + # Classified in SQL so the group count stays small: per + # note we get at most (surfaced-ranked, surfaced-ambient, + # pulled) rather than one row per distinct source. + case( + (NoteUsageEvent.source.in_(AMBIENT_SOURCES), True), + else_=False, + ).label("ambient"), ) .where(NoteUsageEvent.note_id.in_(ids)) - .group_by(NoteUsageEvent.note_id, NoteUsageEvent.event) + .group_by( + NoteUsageEvent.note_id, + NoteUsageEvent.event, + case( + (NoteUsageEvent.source.in_(AMBIENT_SOURCES), True), + else_=False, + ), + ) ) ).all() except Exception: @@ -142,14 +172,21 @@ async def usage_for_notes(note_ids: list[int]) -> dict[int, dict]: logger.debug("note usage readout failed", exc_info=True) return out - for note_id, event, n, last_at in rows: + for note_id, event, n, last_at, ambient in rows: slot = out.get(int(note_id)) if slot is None: continue - if event == SURFACED: + if event == SURFACED and ambient: + slot["ambient_count"] = int(n) + elif event == SURFACED: slot["surfaced_count"] = int(n) slot["last_surfaced_at"] = last_at.isoformat() if last_at else None elif event == PULLED: - slot["pull_count"] = int(n) - slot["last_pulled_at"] = last_at.isoformat() if last_at else None + # Pulls are pulls regardless of what surfaced the record — the + # question a pull answers ("did anyone ever open this?") doesn't + # depend on how it was found. + slot["pull_count"] = slot["pull_count"] + int(n) + latest = last_at.isoformat() if last_at else None + if latest and (slot["last_pulled_at"] or "") < latest: + slot["last_pulled_at"] = latest return out diff --git a/src/scribe/services/plugin_context.py b/src/scribe/services/plugin_context.py index 4e5ce51..64b08e4 100644 --- a/src/scribe/services/plugin_context.py +++ b/src/scribe/services/plugin_context.py @@ -243,6 +243,19 @@ async def build_process_manifest(user_id: int) -> dict: entry["shared"] = True entry["owner"] = it.get("owner") procs.append(entry) + + # The most consequential passive surface Scribe has (see SCOPE above), and + # it emitted nothing — a Process installed as a skill, matched on every + # relevant turn and never once opened, was indistinguishable from one never + # installed (#2477). The honest event is "installed on the operator's + # machine", which is a surfacing in effect: the skill description is in + # front of the model each session. AMBIENT source — installation is not a + # ranked choice — so it lands in ambient_count, not surfaced_count. + record_surfaced( + user_id=user_id, + note_ids=[int(p["id"]) for p in procs], + source="process_skill_sync", + ) return {"processes": procs, "total": len(procs)} diff --git a/tests/test_note_usage.py b/tests/test_note_usage.py index d223e6d..11c6e62 100644 --- a/tests/test_note_usage.py +++ b/tests/test_note_usage.py @@ -113,7 +113,13 @@ async def test_usage_for_notes_splits_counts_by_event(): from datetime import datetime, timezone ts = datetime(2026, 7, 28, tzinfo=timezone.utc) - rows = [(3, "surfaced", 9, ts), (3, "pulled", 2, ts)] + # Rows are (note_id, event, count, last_at, ambient) since #2477 split the + # readout. Ranked and ambient surfacings arrive as separate groups. + rows = [ + (3, "surfaced", 9, ts, False), + (3, "surfaced", 40, ts, True), + (3, "pulled", 2, ts, False), + ] session = MagicMock() session.execute = AsyncMock( return_value=MagicMock(all=MagicMock(return_value=rows)) @@ -123,7 +129,11 @@ async def test_usage_for_notes_splits_counts_by_event(): ctx.__aexit__ = AsyncMock(return_value=False) with patch.object(note_usage, "async_session", return_value=ctx): out = await usage_for_notes([3]) + # The dead-weight reading ("surfaced often, never pulled") is only valid + # over surfacings that were CHOICES. 40 enter_project appearances must not + # make a record look popular — they sit in ambient_count (#2477). assert out[3]["surfaced_count"] == 9 + assert out[3]["ambient_count"] == 40 assert out[3]["pull_count"] == 2 assert out[3]["last_pulled_at"] == ts.isoformat() From 4ba544e2afb82ab9ab980fce5bba83833cd7b14c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 8 Aug 2026 22:42:37 -0400 Subject: [PATCH 5/5] =?UTF-8?q?refactor(theme):=20retire=20the=20--color-*?= =?UTF-8?q?=20shim=20=E2=80=94=20the=20sweep=20it=20promised,=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2533. theme.css claimed "removing this block is a rename sweep across the components, tracked separately" — written in 67a529a, never filed, which made the comment itself an instance of the survey's presence-without-reference pattern. This is that sweep. 73 alias declarations deleted; 69 files rewritten; every --color-*-style name now references its --fs-* token directly. Mechanical by construction: the map IS the alias block, applied longest-name-first with a boundary guard so --color-text never matched inside --color-text-muted. Zero survivors outside theme.css, verified by grep rather than assumed. One deliberate survivor: --color-shadow stays DECLARED, because it was never an alias — it is a literal value the design system has no token for. Marked in place as a recorded gap: promote it to an --fs-* token when a second app needs it, don't copy the line. Nothing is lost mode-wise: the aliases' resolve-at-use-time trick (which absorbed 48 dark-mode overrides) lives one layer down in the --fs-* tokens' own derivations, which is why the sweep is a pure rename. Both CSS checkers green. Why now rather than never: check_snippets_against_design_system reports every --color-* reference as "unknown — renders as NOTHING", and nine recipe snippets recorded from components.css carried the deprecated names, making them prior art pointing the wrong way. With the sweep in, the checker's report over re-recorded snippets should be EMPTY — the acceptance test that proves the checker was right all along (#2517's correction). Refs #2533 --- frontend/src/App.vue | 32 +- frontend/src/assets/components.css | 26 +- frontend/src/assets/editor-shared.css | 172 ++++---- frontend/src/assets/prose.css | 48 +-- frontend/src/assets/theme.css | 127 +----- frontend/src/assets/viewer-shared.css | 24 +- frontend/src/components/AppHeader.vue | 66 +-- frontend/src/components/AppLogo.vue | 10 +- frontend/src/components/DiffView.vue | 32 +- frontend/src/components/HistoryPanel.vue | 52 +-- frontend/src/components/InlineAssistPanel.vue | 68 +-- frontend/src/components/MarkdownToolbar.vue | 22 +- frontend/src/components/MilestoneSelector.vue | 10 +- frontend/src/components/NoteCard.vue | 34 +- frontend/src/components/NotificationBell.vue | 14 +- .../src/components/NotificationsPanel.vue | 18 +- frontend/src/components/PaginationBar.vue | 16 +- frontend/src/components/PriorityBadge.vue | 12 +- frontend/src/components/ProjectDesignTab.vue | 34 +- frontend/src/components/ProjectSelector.vue | 10 +- frontend/src/components/RecurrenceEditor.vue | 12 +- frontend/src/components/SearchBar.vue | 10 +- frontend/src/components/ShareDialog.vue | 52 +-- frontend/src/components/StarterRolePicker.vue | 16 +- frontend/src/components/StatusBadge.vue | 16 +- .../src/components/SuggestionDropdown.vue | 8 +- frontend/src/components/SystemsSection.vue | 116 +++--- frontend/src/components/TableOfContents.vue | 6 +- frontend/src/components/TagInput.vue | 26 +- frontend/src/components/TagPill.vue | 10 +- frontend/src/components/TaskCard.vue | 40 +- frontend/src/components/TaskLogSection.vue | 38 +- frontend/src/components/TiptapEditor.vue | 2 +- frontend/src/components/ToastNotification.vue | 6 +- frontend/src/components/TokenPreview.vue | 40 +- .../src/components/VersionHistorySection.vue | 28 +- frontend/src/components/WordCount.vue | 4 +- .../src/components/WorkspaceNoteEditor.vue | 70 ++-- .../src/components/WorkspaceTaskPanel.vue | 96 ++--- .../src/components/rules/PlanRulesPanel.vue | 4 +- .../src/components/rules/ProjectRulesTab.vue | 34 +- .../components/rules/RuleEditorSlideOver.vue | 10 +- .../src/components/rules/RuleListPane.vue | 6 +- .../components/rules/RulebookDetailPane.vue | 12 +- .../src/components/rules/RulebookListPane.vue | 14 +- frontend/src/utils/palette.ts | 6 +- frontend/src/views/DashboardView.vue | 76 ++-- frontend/src/views/DesignSystemsView.vue | 110 ++--- frontend/src/views/ForgotPasswordView.vue | 26 +- frontend/src/views/GraphView.vue | 118 +++--- frontend/src/views/KnowledgeView.vue | 140 +++---- frontend/src/views/LoginView.vue | 34 +- frontend/src/views/LogsView.vue | 74 ++-- frontend/src/views/NoteEditorView.vue | 70 ++-- frontend/src/views/NoteViewerView.vue | 38 +- frontend/src/views/ProjectListView.vue | 110 ++--- frontend/src/views/ProjectView.vue | 280 ++++++------- frontend/src/views/RegisterInviteView.vue | 34 +- frontend/src/views/RegisterView.vue | 34 +- frontend/src/views/ResetPasswordView.vue | 34 +- frontend/src/views/RulesView.vue | 4 +- frontend/src/views/SettingsView.vue | 394 +++++++++--------- frontend/src/views/SharedWithMeView.vue | 40 +- frontend/src/views/SnippetDetailView.vue | 66 +-- frontend/src/views/SnippetEditorView.vue | 74 ++-- frontend/src/views/SnippetListView.vue | 170 ++++---- frontend/src/views/TaskEditorView.vue | 80 ++-- frontend/src/views/TaskViewerView.vue | 84 ++-- frontend/src/views/TrashView.vue | 6 +- frontend/src/views/UserManagementView.vue | 58 +-- 70 files changed, 1779 insertions(+), 1884 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 044e3d6..2efab18 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -254,7 +254,7 @@ onUnmounted(() => { left: 0.5rem; z-index: 9999; padding: 0.4rem 0.75rem; - background: var(--color-primary); + background: var(--fs-accent); color: var(--fs-text-on-action); border-radius: 0 0 4px 4px; font-size: 0.875rem; @@ -290,7 +290,7 @@ onUnmounted(() => { text-align: center; padding: 0.2rem 0; font-size: 0.68rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); opacity: 0.45; user-select: none; letter-spacing: 0.03em; @@ -300,16 +300,16 @@ onUnmounted(() => { .shortcuts-overlay { position: fixed; inset: 0; - background: var(--color-overlay); + background: var(--fs-overlay); z-index: 9000; display: flex; align-items: center; justify-content: center; } .shortcuts-panel { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); box-shadow: 0 8px 32px var(--color-shadow); width: min(420px, 92vw); overflow: hidden; @@ -319,25 +319,25 @@ onUnmounted(() => { align-items: center; justify-content: space-between; padding: 0.85rem 1rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .shortcuts-header h3 { margin: 0; font-size: 1rem; font-weight: 600; - color: var(--color-text); + color: var(--fs-text-primary); } .shortcuts-close { background: none; border: none; font-size: 1.4rem; line-height: 1; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); cursor: pointer; padding: 0 0.25rem; } .shortcuts-close:hover { - color: var(--color-text); + color: var(--fs-text-primary); } .shortcuts-body { padding: 0.75rem 1rem 1rem; @@ -350,7 +350,7 @@ onUnmounted(() => { font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.4rem; } .shortcut-row { @@ -365,23 +365,23 @@ onUnmounted(() => { justify-content: center; min-width: 1.8rem; padding: 0.15rem 0.4rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-bottom-width: 2px; border-radius: 4px; font-size: 0.78rem; font-family: ui-monospace, monospace; - color: var(--color-text); + color: var(--fs-text-primary); white-space: nowrap; user-select: none; } .shortcut-key-sep { font-size: 0.78rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .shortcut-desc { font-size: 0.875rem; - color: var(--color-text); + color: var(--fs-text-primary); margin-left: 0.25rem; } diff --git a/frontend/src/assets/components.css b/frontend/src/assets/components.css index df298e5..11363cc 100644 --- a/frontend/src/assets/components.css +++ b/frontend/src/assets/components.css @@ -89,19 +89,19 @@ * are universal across the family so a Save button looks identical in every * app — the accent is identity, not action. */ .btn-primary { - background: var(--color-action-primary); + background: var(--fs-action-primary); color: var(--fs-text-on-action); } .btn-primary:not(:disabled):hover { - background: var(--color-action-primary-hover); + background: var(--fs-action-primary-hover); } .btn-secondary { - background: var(--color-action-secondary); + background: var(--fs-action-secondary); color: var(--fs-text-on-action); } .btn-secondary:not(:disabled):hover { - background: var(--color-action-secondary-hover); + background: var(--fs-action-secondary-hover); } /* Ghost is an OUTLINE, which is why its border and the tertiary action colour @@ -112,21 +112,21 @@ .btn-ghost { background: none; border: var(--fs-border); - color: var(--color-text); + color: var(--fs-text-primary); } .btn-ghost:not(:disabled):hover { border: var(--fs-border-hover); - background: var(--color-hover); + background: var(--fs-surface-hover); } /* Destructive is NOT the error colour: an error is a failure that happened, a * destructive action is one about to happen. Pair with an icon. */ .btn-danger { - background: var(--color-action-destructive); + background: var(--fs-action-destructive); color: var(--fs-text-on-action); } .btn-danger:not(:disabled):hover { - background: var(--color-action-destructive-hover); + background: var(--fs-action-destructive-hover); } /* A bare text button: no fill, no border. The most common shape in the dense @@ -136,7 +136,7 @@ .btn-text { background: none; border: none; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: var(--fs-space-1) var(--fs-space-2); font-family: var(--fs-font-body); font-size: var(--fs-size-tiny); @@ -144,7 +144,7 @@ cursor: pointer; transition: color var(--fs-dur-fast) var(--fs-ease); } -.btn-text:not(:disabled):hover { color: var(--color-text); } +.btn-text:not(:disabled):hover { color: var(--fs-text-primary); } .btn-text:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; } .btn-text:focus-visible { outline: none; box-shadow: var(--fs-focus-ring); } @@ -153,11 +153,11 @@ * a one-off: it is what a delete looks like when it must not shout. */ .btn-danger-outline { background: none; - border: 1px solid var(--color-action-destructive); - color: var(--color-action-destructive); + border: 1px solid var(--fs-action-destructive); + color: var(--fs-action-destructive); } .btn-danger-outline:not(:disabled):hover { - background: var(--color-action-destructive); + background: var(--fs-action-destructive); color: var(--fs-text-on-action); } diff --git a/frontend/src/assets/editor-shared.css b/frontend/src/assets/editor-shared.css index fa0af46..626b58b 100644 --- a/frontend/src/assets/editor-shared.css +++ b/frontend/src/assets/editor-shared.css @@ -13,7 +13,7 @@ flex-direction: column; gap: 0.75rem; padding: 1rem 1.5rem 0.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .editor-body { flex: 1; @@ -41,36 +41,36 @@ with a Trash icon at the call site to reinforce intent. */ .title-input:focus { outline: none; - border-bottom-color: var(--color-primary); + border-bottom-color: var(--fs-accent); } .title-input::placeholder { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-weight: 400; } .editor-tabs { display: flex; gap: 0; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .tab { padding: 0.45rem 1rem; border: none; background: none; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); cursor: pointer; font-size: 0.9rem; border-bottom: 2px solid transparent; } .tab.active { - color: var(--color-primary); - border-bottom-color: var(--color-primary); + color: var(--fs-accent); + border-bottom-color: var(--fs-accent); } .preview-pane { padding: 0.75rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); min-height: 200px; - background: var(--color-bg-card); + background: var(--fs-surface-raised); } /* ── Tag suggestions ── */ @@ -85,21 +85,21 @@ align-items: center; gap: 0.2rem; padding: 0.2rem 0.55rem; - border: 1px solid var(--color-primary); + border: 1px solid var(--fs-accent); border-radius: 999px; background: transparent; - color: var(--color-primary); + color: var(--fs-accent); font-size: 0.8rem; cursor: pointer; transition: background 0.15s, color 0.15s; } .tag-pill:hover:not(:disabled) { - background: var(--color-primary); + background: var(--fs-accent); color: var(--fs-text-on-action); } .tag-pill.applied { - background: var(--color-success); - border-color: var(--color-success); + background: var(--fs-success); + border-color: var(--fs-success); color: var(--fs-text-on-action); cursor: default; } @@ -111,8 +111,8 @@ .assist-panel { width: 320px; flex-shrink: 0; - border-left: 1px solid var(--color-border); - background: var(--color-bg-secondary); + border-left: 1px solid var(--fs-border-color); + background: var(--fs-surface-raised); display: flex; flex-direction: column; overflow: hidden; @@ -123,13 +123,13 @@ align-items: center; gap: 0.5rem; padding: 0.65rem 0.9rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .assist-panel-title { flex: 1; font-size: 0.8rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); text-transform: uppercase; letter-spacing: 0.05em; } @@ -149,13 +149,13 @@ font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.2rem; } .assist-sections { - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); max-height: 200px; overflow-y: auto; flex-shrink: 0; @@ -165,46 +165,46 @@ cursor: pointer; font-size: 0.82rem; border-left: 3px solid transparent; - color: var(--color-text); + color: var(--fs-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .assist-section-item:hover { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .assist-section-item.selected { - border-left-color: var(--color-primary); - background: var(--color-bg-secondary); + border-left-color: var(--fs-accent); + background: var(--fs-surface-raised); font-weight: 500; } .assist-empty, .assist-hint { padding: 0.6rem 0.7rem; font-size: 0.82rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .assist-target-preview { font-size: 0.8rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .assist-target-preview em { font-style: normal; - color: var(--color-text); + color: var(--fs-text-primary); } .assist-instruction { width: 100%; padding: 0.5rem 0.65rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.88rem; font-family: inherit; resize: vertical; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; min-height: 3.5rem; } @@ -216,22 +216,22 @@ /* Streaming */ .assist-streaming-label { font-size: 0.8rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .assist-preview-box { padding: 0.65rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); font-size: 0.9rem; max-height: 300px; overflow-y: auto; } .typing-indicator { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.75rem; letter-spacing: 0.15em; animation: blink 1s step-end infinite; @@ -244,18 +244,18 @@ .assist-active-hint { padding: 0.5rem 0.75rem; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); text-align: center; } /* Error */ .assist-error { padding: 0.5rem 0.75rem; - background: color-mix(in srgb, var(--color-danger) 10%, transparent); - border: 1px solid var(--color-danger); - border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--fs-error) 10%, transparent); + border: 1px solid var(--fs-error); + border-radius: var(--fs-radius-sm); font-size: 0.85rem; - color: var(--color-danger); + color: var(--fs-error); } /* Review / diff */ @@ -265,12 +265,12 @@ justify-content: space-between; font-size: 0.8rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .diff-view { - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); font-size: 0.82rem; font-family: monospace; max-height: 340px; @@ -284,15 +284,15 @@ line-height: 1.5; } .diff-delete { - background: color-mix(in srgb, var(--color-danger) 12%, transparent); - color: var(--color-danger); + background: color-mix(in srgb, var(--fs-error) 12%, transparent); + color: var(--fs-error); } .diff-insert { - background: color-mix(in srgb, var(--color-success) 12%, transparent); - color: var(--color-success); + background: color-mix(in srgb, var(--fs-success) 12%, transparent); + color: var(--fs-success); } .diff-equal { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .diff-marker { flex-shrink: 0; @@ -308,7 +308,7 @@ } .diff-empty { padding: 0.5rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.82rem; } .assist-actions { @@ -320,15 +320,15 @@ .modal-overlay { position: fixed; inset: 0; - background: var(--color-overlay); + background: var(--fs-overlay); display: flex; align-items: center; justify-content: center; z-index: 200; } .modal-card { - background: var(--color-bg-card); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border-radius: var(--fs-radius-lg); padding: 1.5rem; max-width: 400px; width: 90%; @@ -340,7 +340,7 @@ } .modal-message { margin: 0 0 1.25rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.95rem; } .modal-actions { @@ -350,17 +350,17 @@ } .modal-btn { padding: 0.45rem 1rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg-card); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); cursor: pointer; font-size: 0.9rem; } .modal-btn-danger { - background: var(--color-danger); + background: var(--fs-error); color: var(--fs-text-on-action); - border-color: var(--color-danger); + border-color: var(--fs-error); } /* ── Floating inline assist button (teleported to body) ── */ @@ -369,10 +369,10 @@ z-index: 100; transform: translateX(-50%); padding: 0.3rem 0.75rem; - background: var(--color-action-primary); + background: var(--fs-action-primary); color: var(--fs-text-on-action); border: none; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); cursor: pointer; font-size: 0.8rem; box-shadow: 0 2px 8px var(--color-shadow); @@ -384,12 +384,12 @@ display: none; width: 100%; padding: 0.6rem 1rem; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); border: none; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.85rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); cursor: pointer; text-align: left; font-family: inherit; @@ -408,7 +408,7 @@ .sb-label { font-size: 0.78rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); text-transform: uppercase; letter-spacing: 0.04em; } @@ -416,10 +416,10 @@ .sb-input { width: 100%; padding: 0.35rem 0.5rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg-card); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); font-size: 0.875rem; font-family: inherit; box-sizing: border-box; @@ -427,11 +427,11 @@ .sb-select:focus, .sb-input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .sb-divider { height: 1px; - background: var(--color-border); + background: var(--fs-border-color); margin: 0.15rem 0; } @media (max-width: 720px) { @@ -452,8 +452,8 @@ width: auto; flex: 0 0 45%; border-left: none; - border-top: 1px solid var(--color-border); - border-radius: var(--radius-md) var(--radius-md) 0 0; + border-top: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg) var(--fs-radius-lg) 0 0; } .editor-header { padding: 0.75rem 1rem 0.5rem; @@ -480,14 +480,14 @@ .btn-accept, .btn-generate, .btn-save { - background: var(--color-action-primary); + background: var(--fs-action-primary); color: var(--fs-text-on-action); border: none; } .btn-accept:not(:disabled):hover, .btn-generate:not(:disabled):hover, .btn-save:not(:disabled):hover { - background: var(--color-action-primary-hover); + background: var(--fs-action-primary-hover); } .btn-back, @@ -497,7 +497,7 @@ .btn-suggest-tags { background: none; border: var(--fs-border); - color: var(--color-text); + color: var(--fs-text-primary); } .btn-back:not(:disabled):hover, .btn-clear:not(:disabled):hover, @@ -505,16 +505,16 @@ .btn-proofread:not(:disabled):hover, .btn-suggest-tags:not(:disabled):hover { border: var(--fs-border-hover); - background: var(--color-hover); + background: var(--fs-surface-hover); } .btn-delete { - background: var(--color-action-destructive); + background: var(--fs-action-destructive); color: var(--fs-text-on-action); border: none; } .btn-delete:not(:disabled):hover { - background: var(--color-action-destructive-hover); + background: var(--fs-action-destructive-hover); } /* Shared geometry for every alias above. */ @@ -541,12 +541,12 @@ .btn-dismiss-tags { background: none; border: none; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 2px var(--fs-space-1); font-size: var(--fs-size-tiny); line-height: 1; } -.btn-dismiss-tags:hover { color: var(--color-text); } +.btn-dismiss-tags:hover { color: var(--fs-text-primary); } .btn-accept:disabled, .btn-generate:disabled, .btn-save:disabled, .btn-back:disabled, .btn-clear:disabled, .btn-reject:disabled, diff --git a/frontend/src/assets/prose.css b/frontend/src/assets/prose.css index 6056d48..ede3b28 100644 --- a/frontend/src/assets/prose.css +++ b/frontend/src/assets/prose.css @@ -41,8 +41,8 @@ } .prose pre { - background: var(--color-code-bg); - border: 1px solid var(--color-border); + background: var(--fs-surface-code); + border: 1px solid var(--fs-border-color); border-radius: 6px; padding: 0.75rem; overflow-x: auto; @@ -57,7 +57,7 @@ } .prose code { - background: var(--color-code-inline-bg); + background: var(--fs-surface-code-inline); border-radius: 3px; padding: 0.15rem 0.35rem; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, @@ -73,25 +73,25 @@ .prose th, .prose td { - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); padding: 0.4rem 0.6rem; text-align: left; } .prose thead th { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); font-weight: 600; } .prose tbody tr:nth-child(even) { - background: var(--color-table-stripe); + background: var(--fs-table-stripe); } .prose blockquote { - border-left: 3px solid var(--color-border); + border-left: 3px solid var(--fs-border-color); margin: 0 0 0.6rem; padding: 0.25rem 0 0.25rem 0.75rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .prose blockquote p:last-child { @@ -100,7 +100,7 @@ .prose hr { border: none; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); margin: 1rem 0; } @@ -110,7 +110,7 @@ } .prose a { - color: var(--color-primary); + color: var(--fs-accent); text-decoration: none; } @@ -119,8 +119,8 @@ } .prose .inline-tag { - color: var(--color-tag-text); - background: var(--color-tag-bg); + color: var(--fs-accent); + background: var(--fs-accent-soft); padding: 0.1rem 0.35rem; border-radius: 4px; text-decoration: none; @@ -133,8 +133,8 @@ } .prose .wikilink { - color: var(--color-wikilink); - background: var(--color-wikilink-bg); + color: var(--fs-wikilink); + background: var(--fs-accent-soft); padding: 0.1rem 0.35rem; border-radius: 4px; text-decoration: none; @@ -168,7 +168,7 @@ .prose ul[data-type="taskList"] li > label input[type="checkbox"] { cursor: pointer; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); width: 0.95em; height: 0.95em; margin: 0; @@ -180,7 +180,7 @@ .prose ul[data-type="taskList"] li[data-checked="true"] > div { text-decoration: line-through; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } /* Interactive checkboxes — marked output in the list-note viewer */ @@ -196,7 +196,7 @@ } .prose--checklist li input[type="checkbox"] { flex-shrink: 0; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); cursor: pointer; width: 0.95em; height: 0.95em; @@ -205,7 +205,7 @@ .prose--checklist li:has(input[type="checkbox"]:checked) > p, .prose--checklist li:has(input[type="checkbox"]:checked) { text-decoration: line-through; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .prose--checklist li:has(input[type="checkbox"]:checked) input[type="checkbox"] { text-decoration: none; /* don't strike through the checkbox itself */ @@ -219,7 +219,7 @@ } .tiptap-editor .ProseMirror p.is-editor-empty:first-child::before { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); content: attr(data-placeholder); float: left; height: 0; @@ -227,12 +227,12 @@ } .tiptap-wrapper { - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg-card); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); } .tiptap-wrapper:focus-within { - box-shadow: var(--focus-ring); + box-shadow: var(--fs-focus-ring); } diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index 2b65328..38467d2 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -188,126 +188,21 @@ */ /* ========================================================================== - COMPATIBILITY ALIASES — the app's historical names, pointing at the system. - - These exist so ~55 components keep working while they migrate to --fs-* - one at a time. Every one is a plain var() reference, which is what lets this - block be declared ONCE: when [data-theme="light"] moves --fs-surface-page, - --color-bg follows, because the alias resolves at use time. - - That is why this file lost 48 of its 60 dark-mode overrides — they were all - restating relationships the aliases now express directly. - - Removing this block is a rename sweep across the components, tracked - separately. Nothing new should reference a --color-* name. + The compatibility-alias block that lived here is GONE (#2533). It let ~55 + components keep their historical --color-* names while theme.css was + repointed at the design system; the rename sweep it promised ran on + 2026-08-08 and every component now references --fs-* directly. Do not + reintroduce app-local alias names — the design system's tokens are the + vocabulary, and check_snippets_against_design_system can only see through + names the system actually declares. ========================================================================== */ :root { - /* surfaces */ - --color-bg: var(--fs-surface-page); - --color-bg-secondary: var(--fs-surface-raised); - --color-bg-card: var(--fs-surface-raised); - --color-surface: var(--fs-surface-hover); - --color-code-bg: var(--fs-surface-code); - --color-code-inline-bg: var(--fs-surface-code-inline); - --color-table-stripe: var(--fs-table-stripe); - --color-overlay: var(--fs-overlay); - - /* text */ - --color-text: var(--fs-text-primary); - --color-text-secondary: var(--fs-text-secondary); - --color-text-muted: var(--fs-text-tertiary); - - /* lines */ - --color-border: var(--fs-border-color); - --color-input-border: var(--fs-border-color); - --focus-ring: var(--fs-focus-ring); - - /* brand */ - --color-primary: var(--fs-accent); - --color-primary-solid: var(--fs-accent); - --color-primary-deep: var(--fs-accent-deep); - --color-primary-faint: var(--fs-accent-faint); - --color-primary-tint: var(--fs-accent-soft); - --color-primary-wash: var(--fs-accent-wash); - --color-tag-bg: var(--fs-accent-soft); - --color-tag-text: var(--fs-accent); - --color-wikilink: var(--fs-wikilink); - --color-wikilink-bg: var(--fs-accent-soft); - --gradient-cta: var(--fs-gradient-cta); - --glow-cta: var(--fs-glow-cta); - --glow-cta-hover: var(--fs-glow-cta-hover); - - /* actions */ - --color-action-primary: var(--fs-action-primary); - --color-action-primary-hover: var(--fs-action-primary-hover); - --color-action-secondary: var(--fs-action-secondary); - --color-action-secondary-hover: var(--fs-action-secondary-hover); - --color-action-destructive: var(--fs-action-destructive); - --color-action-destructive-hover: var(--fs-action-destructive-hover); - - /* semantic */ - --color-success: var(--fs-success); - --color-warning: var(--fs-warning); - --color-danger: var(--fs-error); - --color-overdue: var(--fs-overdue); - --color-toast-success: var(--fs-success); - --color-toast-error: var(--fs-error); + /* A VALUE, not an alias — the one survivor of the alias block. The design + system has no shadow-colour token yet, so this is a recorded gap: when a + second app needs it, promote it to an --fs-* token in the system and + regenerate, rather than copying this line. */ --color-shadow: rgba(0, 0, 0, 0.4); - - /* task status + priority */ - --color-status-todo: var(--fs-status-todo); - --color-status-todo-bg: var(--fs-status-todo-bg); - --color-status-in-progress: var(--fs-status-in-progress); - --color-status-in-progress-bg: var(--fs-status-in-progress-bg); - --color-status-done: var(--fs-status-done); - --color-status-done-bg: var(--fs-status-done-bg); - --color-priority-low: var(--fs-priority-low); - --color-priority-low-bg: var(--fs-priority-low-bg); - --color-priority-medium: var(--fs-priority-medium); - --color-priority-medium-bg: var(--fs-priority-medium-bg); - --color-priority-high: var(--fs-priority-high); - --color-priority-high-bg: var(--fs-priority-high-bg); - - /* geometry */ - --radius-sm: var(--fs-radius-sm); - --radius-md: var(--fs-radius-lg); /* NB: the app's "md" is the system's LARGE */ - --radius-lg: var(--fs-radius-xl); /* and the app's "lg" is the system's XL */ - --page-max-width: var(--fs-layout-page-max); - --page-padding-x: var(--fs-layout-page-pad); - --sidebar-width: var(--fs-layout-sidebar); - --header-height: var(--fs-layout-header); - - /* ------------------------------------------------------------------ - Names components reference that were NEVER declared anywhere. - - Each of these was reached for with a hardcoded fallback, so the page - rendered — but the fallback was what rendered, always, and several were - off-palette: --color-primary-bg fell back to an indigo, --color-destructive - to a brick that is not the oxblood, --color-status-cancelled to a grey from - no palette in this system. - - Wiring them to real tokens is the whole point of the exercise. Expect small - visual shifts exactly where a fallback had drifted; that shift IS the fix. - ------------------------------------------------------------------ */ - --color-accent: var(--fs-accent); - /* Foreground ON the accent, so it follows the accent's mode-independence, - not the page text's. Pointing this at --fs-text-primary made it invert to - obsidian on light — over a mid-tone accent, well under the AA floor. */ - --color-accent-fg: var(--fs-text-on-action); - --color-hover: var(--fs-surface-hover); - --color-bg-hover: var(--fs-surface-hover); - --color-bg-tertiary: var(--fs-surface-hover); - --color-surface-2: var(--fs-surface-hover); - --color-surface-alt: var(--fs-surface-hover); - --color-surface-raised: var(--fs-surface-raised); - --color-input-bg: var(--fs-surface-page); - --color-muted: var(--fs-text-tertiary); - --color-destructive: var(--fs-destructive); - --color-primary-bg: var(--fs-accent-soft); - --color-status-cancelled: var(--fs-status-cancelled); - --font-display: var(--fs-font-display); - --font-mono: var(--fs-font-mono); } /* ========================================================================== diff --git a/frontend/src/assets/viewer-shared.css b/frontend/src/assets/viewer-shared.css index d643d08..644fe1d 100644 --- a/frontend/src/assets/viewer-shared.css +++ b/frontend/src/assets/viewer-shared.css @@ -15,27 +15,27 @@ white-space: nowrap; } .ctx-crumb-parent { - color: var(--color-text-muted); - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + color: var(--fs-text-tertiary); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); text-decoration: none; } .ctx-crumb-parent:hover { - color: var(--color-primary); - border-color: var(--color-primary); + color: var(--fs-accent); + border-color: var(--fs-accent); } .ctx-crumb-project { - color: var(--color-primary); - background: color-mix(in srgb, var(--color-primary) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent); + color: var(--fs-accent); + background: color-mix(in srgb, var(--fs-accent) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent); text-decoration: none; font-weight: 500; } .ctx-crumb-project:hover { - background: color-mix(in srgb, var(--color-primary) 18%, transparent); + background: color-mix(in srgb, var(--fs-accent) 18%, transparent); } .ctx-crumb-milestone { - color: var(--color-text-secondary); - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + color: var(--fs-text-secondary); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); } diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue index c40d0f7..3066255 100644 --- a/frontend/src/components/AppHeader.vue +++ b/frontend/src/components/AppHeader.vue @@ -124,8 +124,8 @@ router.afterEach(() => { diff --git a/frontend/src/components/DiffView.vue b/frontend/src/components/DiffView.vue index 01fb67f..b2d4dde 100644 --- a/frontend/src/components/DiffView.vue +++ b/frontend/src/components/DiffView.vue @@ -90,9 +90,9 @@ function markerFor(type: DiffLine['type']): string { flex-direction: column; flex: 1; min-height: 0; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); overflow: hidden; } @@ -103,15 +103,15 @@ function markerFor(type: DiffLine['type']): string { display: flex; gap: 1rem; padding: 0.4rem 0.75rem; - background: var(--color-bg-secondary); - border-bottom: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.78rem; font-family: monospace; font-weight: 600; } -.diff-summary-ins { color: var(--color-success); } -.diff-summary-del { color: var(--color-danger); } +.diff-summary-ins { color: var(--fs-success); } +.diff-summary-del { color: var(--fs-error); } .diff-scroll { flex: 1; @@ -123,7 +123,7 @@ function markerFor(type: DiffLine['type']): string { .diff-empty { padding: 0.75rem; font-size: 0.85rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .diff-line { @@ -136,24 +136,24 @@ function markerFor(type: DiffLine['type']): string { } .diff-delete { - background: color-mix(in srgb, var(--color-danger) 12%, transparent); - color: var(--color-danger); + background: color-mix(in srgb, var(--fs-error) 12%, transparent); + color: var(--fs-error); } .diff-insert { - background: color-mix(in srgb, var(--color-success) 12%, transparent); - color: var(--color-success); + background: color-mix(in srgb, var(--fs-success) 12%, transparent); + color: var(--fs-success); } .diff-equal { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .diff-collapse { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); opacity: 0.6; - border-top: 1px dashed var(--color-border); - border-bottom: 1px dashed var(--color-border); + border-top: 1px dashed var(--fs-border-color); + border-bottom: 1px dashed var(--fs-border-color); padding-top: 0.2rem; padding-bottom: 0.2rem; } diff --git a/frontend/src/components/HistoryPanel.vue b/frontend/src/components/HistoryPanel.vue index 4b2211f..f1549ab 100644 --- a/frontend/src/components/HistoryPanel.vue +++ b/frontend/src/components/HistoryPanel.vue @@ -309,7 +309,7 @@ onMounted(loadVersions); align-items: center; justify-content: space-between; padding: 0.9rem 1.25rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .history-title { @@ -322,11 +322,11 @@ onMounted(loadVersions); border: none; font-size: 1.25rem; cursor: pointer; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); line-height: 1; padding: 0.1rem 0.3rem; } -.history-close:hover { color: var(--color-text); } +.history-close:hover { color: var(--fs-text-primary); } .history-body { flex: 1; @@ -338,7 +338,7 @@ onMounted(loadVersions); .history-list { width: 220px; flex-shrink: 0; - border-right: 1px solid var(--color-border); + border-right: 1px solid var(--fs-border-color); overflow-y: auto; } @@ -346,18 +346,18 @@ onMounted(loadVersions); padding: 0.6rem 0.9rem; cursor: pointer; border-left: 3px solid transparent; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } -.history-item:hover { background: var(--color-bg-secondary); } +.history-item:hover { background: var(--fs-surface-raised); } .history-item.selected { - border-left-color: var(--color-primary); - background: var(--color-bg-secondary); + border-left-color: var(--fs-accent); + background: var(--fs-surface-raised); } .history-item-title { font-size: 0.85rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -365,7 +365,7 @@ onMounted(loadVersions); .history-item-date { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-top: 0.15rem; } @@ -381,7 +381,7 @@ onMounted(loadVersions); .history-empty { padding: 1rem; font-size: 0.85rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .history-footer { @@ -390,7 +390,7 @@ onMounted(loadVersions); gap: 0.5rem; justify-content: flex-end; padding: 0.75rem 1.25rem; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); } @@ -403,12 +403,12 @@ onMounted(loadVersions); font-size: 0.85em; line-height: 1; } -.pin-badge-manual { color: var(--color-primary); } -.pin-badge-auto { color: var(--color-text-muted); } +.pin-badge-manual { color: var(--fs-accent); } +.pin-badge-auto { color: var(--fs-text-tertiary); } .history-item-label { font-size: 0.72rem; - color: var(--color-primary); + color: var(--fs-accent); font-style: italic; margin-top: 0.15rem; overflow: hidden; @@ -419,7 +419,7 @@ onMounted(loadVersions); /* ── Pin controls above the diff ────────────────────────────────────────── */ .version-pin-controls { padding: 0.4rem 0.5rem 0.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.82rem; } .pin-actions { @@ -430,7 +430,7 @@ onMounted(loadVersions); } .pin-state { font-style: italic; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); flex: 1; min-width: 0; overflow: hidden; @@ -442,13 +442,13 @@ onMounted(loadVersions); font-size: 0.78rem; background: transparent; color: inherit; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 999px; cursor: pointer; } .btn-pin:hover:not(:disabled), .btn-pin-edit:hover:not(:disabled) { background: rgba(99, 102, 241, 0.12); - border-color: var(--color-primary); + border-color: var(--fs-accent); } .btn-unpin:hover:not(:disabled) { background: rgba(239, 68, 68, 0.10); @@ -463,27 +463,27 @@ onMounted(loadVersions); flex: 1; padding: 0.3rem 0.5rem; font-size: 0.85rem; - background: var(--color-input-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); color: inherit; } .pin-label-input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .btn-pin-save, .btn-pin-cancel { padding: 0.3rem 0.7rem; font-size: 0.78rem; background: transparent; color: inherit; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); cursor: pointer; } .btn-pin-save:hover:not(:disabled) { background: rgba(99, 102, 241, 0.12); - border-color: var(--color-primary); + border-color: var(--fs-accent); } .btn-pin-save:disabled, .btn-pin-cancel:disabled, .btn-pin:disabled, .btn-pin-edit:disabled, .btn-unpin:disabled { diff --git a/frontend/src/components/InlineAssistPanel.vue b/frontend/src/components/InlineAssistPanel.vue index 741a5e0..313008b 100644 --- a/frontend/src/components/InlineAssistPanel.vue +++ b/frontend/src/components/InlineAssistPanel.vue @@ -74,11 +74,11 @@ const markers: Record = { diff --git a/frontend/src/components/NotificationBell.vue b/frontend/src/components/NotificationBell.vue index d3318d5..a1c20a1 100644 --- a/frontend/src/components/NotificationBell.vue +++ b/frontend/src/components/NotificationBell.vue @@ -60,11 +60,11 @@ onUnmounted(() => { .btn-bell { background: none; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); padding: 0.25rem 0.45rem; cursor: pointer; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); display: flex; align-items: center; justify-content: center; @@ -72,16 +72,16 @@ onUnmounted(() => { } .btn-bell:hover, .btn-bell.active { - background: var(--color-bg-card); - color: var(--color-text); - border-color: var(--color-primary); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); + border-color: var(--fs-accent); } .bell-badge { position: absolute; top: -5px; right: -5px; - background: var(--color-danger); + background: var(--fs-error); color: var(--fs-text-on-action); font-size: 0.6rem; font-weight: 700; diff --git a/frontend/src/components/NotificationsPanel.vue b/frontend/src/components/NotificationsPanel.vue index ce9eb6f..8e2f1d9 100644 --- a/frontend/src/components/NotificationsPanel.vue +++ b/frontend/src/components/NotificationsPanel.vue @@ -85,9 +85,9 @@ onMounted(() => store.fetchAll()) width: 340px; max-height: 400px; overflow-y: auto; - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-xl); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18); z-index: 500; } @@ -97,10 +97,10 @@ onMounted(() => store.fetchAll()) align-items: center; justify-content: space-between; padding: 0.75rem 1rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); position: sticky; top: 0; - background: var(--color-surface); + background: var(--fs-surface-hover); } .notif-panel-title { @@ -114,12 +114,12 @@ onMounted(() => store.fetchAll()) align-items: flex-start; gap: 0.6rem; padding: 0.75rem 1rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); cursor: pointer; transition: background 0.1s; } .notif-item:last-child { border-bottom: none; } -.notif-item:hover { background: var(--color-hover); } +.notif-item:hover { background: var(--fs-surface-hover); } .notif-icon { font-size: 1.2rem; flex-shrink: 0; margin-top: 0.1rem; } @@ -127,10 +127,10 @@ onMounted(() => store.fetchAll()) .notif-msg { margin: 0 0 0.2rem; font-size: 0.85rem; - color: var(--color-text); + color: var(--fs-text-primary); line-height: 1.4; word-break: break-word; } -.notif-time { font-size: 0.75rem; color: var(--color-muted); } +.notif-time { font-size: 0.75rem; color: var(--fs-text-tertiary); } diff --git a/frontend/src/components/PaginationBar.vue b/frontend/src/components/PaginationBar.vue index f55f702..398669d 100644 --- a/frontend/src/components/PaginationBar.vue +++ b/frontend/src/components/PaginationBar.vue @@ -74,27 +74,27 @@ function goToPage(page: number) { } .page-btn { padding: 0.35rem 0.7rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg-card); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); cursor: pointer; font-size: 0.85rem; } .page-btn:hover:not(:disabled) { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .page-btn:disabled { opacity: 0.4; cursor: default; } .page-btn.active { - background: var(--color-primary); + background: var(--fs-accent); color: var(--fs-text-on-action); - border-color: var(--color-primary); + border-color: var(--fs-accent); } .ellipsis { padding: 0 0.25rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } diff --git a/frontend/src/components/PriorityBadge.vue b/frontend/src/components/PriorityBadge.vue index f4f0021..22d732f 100644 --- a/frontend/src/components/PriorityBadge.vue +++ b/frontend/src/components/PriorityBadge.vue @@ -33,15 +33,15 @@ const labels: Record = { letter-spacing: 0.025em; } .priority-low { - background: var(--color-priority-low-bg); - color: var(--color-priority-low); + background: var(--fs-priority-low-bg); + color: var(--fs-priority-low); } .priority-medium { - background: var(--color-priority-medium-bg); - color: var(--color-priority-medium); + background: var(--fs-priority-medium-bg); + color: var(--fs-priority-medium); } .priority-high { - background: var(--color-priority-high-bg); - color: var(--color-priority-high); + background: var(--fs-priority-high-bg); + color: var(--fs-priority-high); } diff --git a/frontend/src/components/ProjectDesignTab.vue b/frontend/src/components/ProjectDesignTab.vue index 01f9bd4..fddba0b 100644 --- a/frontend/src/components/ProjectDesignTab.vue +++ b/frontend/src/components/ProjectDesignTab.vue @@ -128,16 +128,16 @@ watch(() => [props.projectId, props.designSystemId], run); } .pdt-note { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-left: 3px solid var(--color-warning); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); + border-left: 3px solid var(--fs-warning); border-radius: var(--fs-radius-sm); padding: var(--fs-space-3) var(--fs-space-4); } .pdt-note p { margin: var(--fs-space-2) 0 0; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: var(--fs-size-body-sm); line-height: var(--fs-leading-body); max-width: 70ch; @@ -146,18 +146,18 @@ watch(() => [props.projectId, props.designSystemId], run); .pdt-muted, .pdt-clean, .pdt-summary { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: var(--fs-size-body-sm); margin: 0 0 var(--fs-space-3); max-width: 70ch; } .pdt-clean { - color: var(--color-status-done); + color: var(--fs-status-done); } .pdt-summary { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .pdt-list { @@ -170,7 +170,7 @@ watch(() => [props.projectId, props.designSystemId], run); } .pdt-finding { - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md); padding: var(--fs-space-3); min-width: 0; @@ -179,11 +179,11 @@ watch(() => [props.projectId, props.designSystemId], run); .pdt-title { display: block; font-weight: var(--fs-weight-medium); - color: var(--color-text); + color: var(--fs-text-primary); text-decoration: none; margin-bottom: var(--fs-space-2); } -.pdt-title:hover { color: var(--color-primary-solid); } +.pdt-title:hover { color: var(--fs-accent); } .pdt-row { display: flex; @@ -205,18 +205,18 @@ watch(() => [props.projectId, props.designSystemId], run); } .pdt-tag.unknown { - background: var(--color-priority-high-bg); - color: var(--color-priority-high); + background: var(--fs-priority-high-bg); + color: var(--fs-priority-high); } .pdt-tag.local { - background: var(--color-priority-medium-bg); - color: var(--color-priority-medium); + background: var(--fs-priority-medium-bg); + color: var(--fs-priority-medium); } .pdt-tag.superseded { - background: var(--color-surface); - color: var(--color-text-muted); + background: var(--fs-surface-hover); + color: var(--fs-text-tertiary); } .pdt-detail { @@ -224,7 +224,7 @@ watch(() => [props.projectId, props.designSystemId], run); flex-wrap: wrap; gap: var(--fs-space-2); font-size: var(--fs-size-code); - color: var(--color-text-secondary); + color: var(--fs-text-secondary); min-width: 0; } diff --git a/frontend/src/components/ProjectSelector.vue b/frontend/src/components/ProjectSelector.vue index 9399eef..430385a 100644 --- a/frontend/src/components/ProjectSelector.vue +++ b/frontend/src/components/ProjectSelector.vue @@ -51,10 +51,10 @@ function onChange(e: Event) { diff --git a/frontend/src/components/RecurrenceEditor.vue b/frontend/src/components/RecurrenceEditor.vue index 865e054..296145f 100644 --- a/frontend/src/components/RecurrenceEditor.vue +++ b/frontend/src/components/RecurrenceEditor.vue @@ -153,19 +153,19 @@ const calendarDayMax = computed(() => } .rec-label { font-size: 0.78rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); min-width: 2.5rem; } .rec-num-input { width: 4rem; padding: 0.25rem 0.4rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); - background: var(--color-bg); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); + color: var(--fs-text-primary); font-size: 0.85rem; font-family: inherit; } -.rec-num-input:focus { outline: none; border-color: var(--color-primary); } +.rec-num-input:focus { outline: none; border-color: var(--fs-accent); } .rec-unit { min-width: 6rem; } diff --git a/frontend/src/components/SearchBar.vue b/frontend/src/components/SearchBar.vue index 701954c..7c7c3ff 100644 --- a/frontend/src/components/SearchBar.vue +++ b/frontend/src/components/SearchBar.vue @@ -28,14 +28,14 @@ defineExpose({ focus: () => inputRef.value?.focus() }); .search-input { width: 100%; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 1rem; box-sizing: border-box; - background: var(--color-bg-card); - color: var(--color-text); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); } .search-input::placeholder { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } diff --git a/frontend/src/components/ShareDialog.vue b/frontend/src/components/ShareDialog.vue index a76278d..e008264 100644 --- a/frontend/src/components/ShareDialog.vue +++ b/frontend/src/components/ShareDialog.vue @@ -206,9 +206,9 @@ onMounted(async () => { } .share-dialog { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-xl); width: 480px; max-width: 95vw; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); @@ -220,7 +220,7 @@ onMounted(async () => { align-items: center; justify-content: space-between; padding: 1.25rem 1.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .share-title { @@ -228,7 +228,7 @@ onMounted(async () => { font-size: 1.1rem; font-weight: 700; margin: 0; - color: var(--color-text); + color: var(--fs-text-primary); } @@ -240,17 +240,17 @@ onMounted(async () => { .share-tab { background: none; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 6px; padding: 0.3rem 0.8rem; font-size: 0.82rem; cursor: pointer; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); transition: all 0.15s; } .share-tab.active { - background: var(--color-primary); - border-color: var(--color-primary); + background: var(--fs-accent); + border-color: var(--fs-accent); color: var(--fs-text-on-action); } @@ -269,23 +269,23 @@ onMounted(async () => { .share-input { width: 100%; padding: 0.45rem 0.7rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 6px; - background: var(--color-bg-card); - color: var(--color-text); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); font-size: 0.9rem; outline: none; transition: border-color 0.15s; } -.share-input:focus { border-color: var(--color-primary); } +.share-input:focus { border-color: var(--fs-accent); } .user-results { position: absolute; top: 100%; left: 0; right: 0; - background: var(--color-bg-card); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 6px; margin-top: 2px; list-style: none; @@ -304,24 +304,24 @@ onMounted(async () => { cursor: pointer; transition: background 0.1s; } -.user-result-item:hover { background: var(--color-bg-secondary); } +.user-result-item:hover { background: var(--fs-surface-raised); } .user-result-name { font-weight: 600; font-size: 0.88rem; } -.user-result-email { color: var(--color-text-muted); font-size: 0.8rem; } +.user-result-email { color: var(--fs-text-tertiary); font-size: 0.8rem; } .perm-select { padding: 0.45rem 0.5rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 6px; - background: var(--color-bg-card); - color: var(--color-text); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); font-size: 0.85rem; cursor: pointer; } .btn-add-share { padding: 0.45rem 1rem; - background: var(--gradient-cta); + background: var(--fs-gradient-cta); color: var(--fs-text-on-action); border: none; border-radius: 6px; @@ -342,7 +342,7 @@ onMounted(async () => { font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.5rem; } @@ -361,7 +361,7 @@ onMounted(async () => { gap: 0.5rem; padding: 0.5rem 0.75rem; border-radius: 8px; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .share-target-icon { font-size: 1rem; flex-shrink: 0; } @@ -369,10 +369,10 @@ onMounted(async () => { .perm-select-inline { padding: 0.25rem 0.4rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 4px; - background: var(--color-bg-card); - color: var(--color-text); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); font-size: 0.8rem; cursor: pointer; } diff --git a/frontend/src/components/StarterRolePicker.vue b/frontend/src/components/StarterRolePicker.vue index 0011fdb..4efb17b 100644 --- a/frontend/src/components/StarterRolePicker.vue +++ b/frontend/src/components/StarterRolePicker.vue @@ -127,7 +127,7 @@ const totalTokens = () => .srp-legend { font-size: var(--fs-size-label); font-weight: var(--fs-weight-medium); - color: var(--color-text); + color: var(--fs-text-primary); padding: 0 var(--fs-space-2); } @@ -135,7 +135,7 @@ const totalTokens = () => .srp-note { margin: 0 0 var(--fs-space-3); font-size: var(--fs-size-body-sm); - color: var(--color-text-secondary); + color: var(--fs-text-secondary); line-height: var(--fs-leading-body); max-width: 62ch; } @@ -160,16 +160,16 @@ const totalTokens = () => cursor: pointer; min-width: 0; } -.srp-item:hover { background: var(--color-hover); } +.srp-item:hover { background: var(--fs-surface-hover); } .srp-name { font-size: var(--fs-size-body-sm); - color: var(--color-text); + color: var(--fs-text-primary); } .srp-count { font-size: var(--fs-size-tiny); - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-variant-numeric: tabular-nums; } @@ -178,7 +178,7 @@ const totalTokens = () => .srp-desc { grid-column: 1 / -1; font-size: var(--fs-size-tiny); - color: var(--color-text-muted); + color: var(--fs-text-tertiary); line-height: var(--fs-leading-body); } @@ -196,7 +196,7 @@ const totalTokens = () => align-items: center; gap: var(--fs-space-2); font-size: var(--fs-size-body-sm); - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .srp-prefix-input { @@ -207,6 +207,6 @@ const totalTokens = () => .srp-total { font-size: var(--fs-size-tiny); - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } diff --git a/frontend/src/components/StatusBadge.vue b/frontend/src/components/StatusBadge.vue index 4e3ca2e..db36296 100644 --- a/frontend/src/components/StatusBadge.vue +++ b/frontend/src/components/StatusBadge.vue @@ -38,20 +38,20 @@ const labels: Record = { letter-spacing: 0.025em; } .status-todo { - background: color-mix(in srgb, var(--color-status-todo-bg) 78%, var(--color-status-todo) 22%); - color: color-mix(in srgb, var(--color-status-todo) 85%, #000 15%); + background: color-mix(in srgb, var(--fs-status-todo-bg) 78%, var(--fs-status-todo) 22%); + color: color-mix(in srgb, var(--fs-status-todo) 85%, #000 15%); } .status-in_progress { - background: color-mix(in srgb, var(--color-status-in-progress-bg) 78%, var(--color-status-in-progress) 22%); - color: color-mix(in srgb, var(--color-status-in-progress) 85%, #000 15%); + background: color-mix(in srgb, var(--fs-status-in-progress-bg) 78%, var(--fs-status-in-progress) 22%); + color: color-mix(in srgb, var(--fs-status-in-progress) 85%, #000 15%); } .status-done { - background: color-mix(in srgb, var(--color-status-done-bg) 78%, var(--color-status-done) 22%); - color: color-mix(in srgb, var(--color-status-done) 85%, #000 15%); + background: color-mix(in srgb, var(--fs-status-done-bg) 78%, var(--fs-status-done) 22%); + color: color-mix(in srgb, var(--fs-status-done) 85%, #000 15%); } .status-cancelled { - background: color-mix(in srgb, var(--color-bg-secondary) 78%, var(--color-text-muted) 22%); - color: var(--color-text-muted); + background: color-mix(in srgb, var(--fs-surface-raised) 78%, var(--fs-text-tertiary) 22%); + color: var(--fs-text-tertiary); } .clickable { cursor: pointer; diff --git a/frontend/src/components/SuggestionDropdown.vue b/frontend/src/components/SuggestionDropdown.vue index bb00933..3e28cde 100644 --- a/frontend/src/components/SuggestionDropdown.vue +++ b/frontend/src/components/SuggestionDropdown.vue @@ -70,9 +70,9 @@ defineExpose({ onKeyDown }); list-style: none; margin: 0; padding: 0; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg-card); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); box-shadow: 0 4px 12px var(--color-shadow); max-height: 200px; overflow-y: auto; @@ -85,6 +85,6 @@ defineExpose({ onKeyDown }); } .ac-item:hover, .ac-item.active { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } diff --git a/frontend/src/components/SystemsSection.vue b/frontend/src/components/SystemsSection.vue index 4a4274d..5d9d5ac 100644 --- a/frontend/src/components/SystemsSection.vue +++ b/frontend/src/components/SystemsSection.vue @@ -253,7 +253,7 @@ async function confirmDelete() { diff --git a/frontend/src/views/DesignSystemsView.vue b/frontend/src/views/DesignSystemsView.vue index 4a3d399..752f51b 100644 --- a/frontend/src/views/DesignSystemsView.vue +++ b/frontend/src/views/DesignSystemsView.vue @@ -1115,7 +1115,7 @@ function isSelfContainedColour(value: string): boolean { } .lede { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); max-width: 70ch; line-height: 1.6; margin: 0 0 1.5rem; @@ -1137,9 +1137,9 @@ function isSelfContainedColour(value: string): boolean { /* Sidebar ---------------------------------------------------------------- */ .ds-sidebar { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1rem; } @@ -1156,7 +1156,7 @@ function isSelfContainedColour(value: string): boolean { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .system-list { @@ -1173,22 +1173,22 @@ function isSelfContainedColour(value: string): boolean { text-align: left; background: none; border: 1px solid transparent; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); padding: 0.5rem 0.6rem; cursor: pointer; - color: var(--color-text); + color: var(--fs-text-primary); display: flex; flex-direction: column; gap: 0.15rem; } .system-btn:hover { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .system-btn.active { - border-color: var(--color-primary); - background: var(--color-bg-secondary); + border-color: var(--fs-accent); + background: var(--fs-surface-raised); } @@ -1198,7 +1198,7 @@ function isSelfContainedColour(value: string): boolean { .system-kind { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } /* Sections --------------------------------------------------------------- */ @@ -1211,9 +1211,9 @@ function isSelfContainedColour(value: string): boolean { } .ds-section { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1.25rem; } @@ -1232,7 +1232,7 @@ function isSelfContainedColour(value: string): boolean { .section-note, .muted { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.85rem; margin: 0 0 1rem; } @@ -1247,17 +1247,17 @@ function isSelfContainedColour(value: string): boolean { } .chain-link { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .chain-link.self { - color: var(--color-text); + color: var(--fs-text-primary); font-weight: 500; } .chain-arrow, .chain-note { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } /* Fields ----------------------------------------------------------------- */ @@ -1282,24 +1282,24 @@ function isSelfContainedColour(value: string): boolean { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.3rem; } .field-hint { margin: 0.3rem 0 0; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); line-height: 1.5; } .input { width: 100%; padding: 0.45rem 0.6rem; - background: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - color: var(--color-text); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + color: var(--fs-text-primary); font: inherit; } @@ -1328,15 +1328,15 @@ textarea.input { .confirm-copy { font-size: 0.85rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); max-width: 40ch; } /* Tokens ----------------------------------------------------------------- */ .token-form { - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); padding: 1rem; margin-bottom: 1rem; } @@ -1371,7 +1371,7 @@ textarea.input { align-items: center; gap: 0.6rem; padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .token-name { @@ -1386,7 +1386,7 @@ textarea.input { } .token-meta { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; flex: 1; min-width: 0; @@ -1405,18 +1405,18 @@ textarea.input { .finding-list li { padding: 0.6rem 0; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .finding-title { font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); } .finding-line { margin: 0.3rem 0 0; font-size: 0.85rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); display: flex; flex-wrap: wrap; align-items: center; @@ -1428,24 +1428,24 @@ textarea.input { text-transform: uppercase; letter-spacing: 0.06em; padding: 0.1rem 0.45rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); flex: none; } .spec-status.violated { - background: var(--color-priority-high-bg); - color: var(--color-priority-high); + background: var(--fs-priority-high-bg); + color: var(--fs-priority-high); } .spec-status.missing { - background: var(--color-priority-medium-bg); - color: var(--color-priority-medium); + background: var(--fs-priority-medium-bg); + color: var(--fs-priority-medium); } .sheet { - background: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); padding: 0.75rem 1rem; margin-top: 0.75rem; overflow-x: auto; @@ -1472,7 +1472,7 @@ textarea.input { .supersedes { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-style: italic; } @@ -1484,7 +1484,7 @@ textarea.input { } .mode-name { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.06em; @@ -1495,7 +1495,7 @@ textarea.input { width: 0.9rem; height: 0.9rem; border-radius: 3px; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); flex: none; } @@ -1510,12 +1510,12 @@ textarea.input { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .resolved-list li { padding: 0.5rem 0; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .resolved-head { @@ -1551,33 +1551,33 @@ textarea.input { text-transform: uppercase; letter-spacing: 0.06em; padding: 0.1rem 0.45rem; - border-radius: var(--radius-sm); - background: var(--color-bg-secondary); - color: var(--color-text-muted); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-raised); + color: var(--fs-text-tertiary); } .origin-badge.own { - background: var(--color-primary-tint); - color: var(--color-primary); + background: var(--fs-accent-soft); + color: var(--fs-accent); } /* Notices ---------------------------------------------------------------- */ .notice { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1.25rem; max-width: 60ch; } .notice-warn { - border-left: 3px solid var(--color-warning); + border-left: 3px solid var(--fs-warning); } .notice p { margin: 0.5rem 0 1rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); line-height: 1.6; } diff --git a/frontend/src/views/ForgotPasswordView.vue b/frontend/src/views/ForgotPasswordView.vue index 8afdca5..198a0c8 100644 --- a/frontend/src/views/ForgotPasswordView.vue +++ b/frontend/src/views/ForgotPasswordView.vue @@ -78,9 +78,9 @@ async function handleSubmit() { .auth-card { width: 100%; max-width: 400px; - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 2rem; } .auth-brand { @@ -97,7 +97,7 @@ async function handleSubmit() { .auth-hint { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin-bottom: 1rem; } .field { @@ -112,25 +112,25 @@ async function handleSubmit() { .input { width: 100%; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; } .input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; margin: 0 0 0.75rem; } .success-msg { text-align: center; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.95rem; padding: 0.5rem 0; } @@ -140,10 +140,10 @@ async function handleSubmit() { .auth-footer { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin: 1rem 0 0; } .auth-footer a { - color: var(--color-primary); + color: var(--fs-accent); } diff --git a/frontend/src/views/GraphView.vue b/frontend/src/views/GraphView.vue index 273fa49..6642522 100644 --- a/frontend/src/views/GraphView.vue +++ b/frontend/src/views/GraphView.vue @@ -208,7 +208,7 @@ function initGraph() { .attr("orient", "auto") .append("path") .attr("d", "M0,-4L8,0L0,4") - .attr("fill", "var(--color-primary)") + .attr("fill", "var(--fs-accent)") .attr("opacity", "0.6"); // Zoom layer @@ -237,7 +237,7 @@ function initGraph() { .append("line") .attr("class", "graph-edge") .attr("stroke", (d: any) => - d.type === "wikilink" ? "var(--color-primary)" : "var(--color-text-muted)" + d.type === "wikilink" ? "var(--fs-accent)" : "var(--fs-text-tertiary)" ) .attr("stroke-opacity", (d: any) => (d.type === "wikilink" ? 0.5 : 0.4)) .attr("stroke-dasharray", null) @@ -295,11 +295,11 @@ function initGraph() { .append("circle") .attr("r", (d: GraphNode) => d.radius ?? 8) .attr("fill", (d: GraphNode) => { - if (d.type === "tag") return "color-mix(in srgb, var(--color-primary) 20%, transparent)"; - return d.project_color ?? "var(--color-bg-secondary)"; + if (d.type === "tag") return "color-mix(in srgb, var(--fs-accent) 20%, transparent)"; + return d.project_color ?? "var(--fs-surface-raised)"; }) .attr("stroke", (d: GraphNode) => - d.type === "tag" ? "var(--color-primary)" : "var(--color-border)" + d.type === "tag" ? "var(--fs-accent)" : "var(--fs-border-color)" ) .attr("stroke-width", (d: GraphNode) => (d.type === "tag" ? 1.5 : 1.5)) .attr("stroke-dasharray", (d: GraphNode) => (d.type === "task" ? "3" : null)) @@ -317,7 +317,7 @@ function initGraph() { .attr("dy", (d: GraphNode) => (d.radius ?? 8) + 12) .attr("font-size", "10px") .attr("fill", (d: GraphNode) => - d.type === "tag" ? "var(--color-primary)" : "var(--color-text-secondary)" + d.type === "tag" ? "var(--fs-accent)" : "var(--fs-text-secondary)" ) .attr("pointer-events", "none"); @@ -600,7 +600,7 @@ onUnmounted(() => { .graph-page { display: flex; flex-direction: column; - height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--fs-layout-header)); overflow: hidden; } @@ -609,17 +609,17 @@ onUnmounted(() => { align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; - background: var(--color-bg-secondary); - border-bottom: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border-bottom: 1px solid var(--fs-border-color); flex-shrink: 0; flex-wrap: wrap; } .graph-select { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - color: var(--color-text); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + color: var(--fs-text-primary); font-size: 0.875rem; padding: 0.3rem 0.6rem; cursor: pointer; @@ -631,27 +631,27 @@ onUnmounted(() => { align-items: center; gap: 0.35rem; font-size: 0.875rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); cursor: pointer; user-select: none; } .graph-toggle input { cursor: pointer; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); } .graph-stats { margin-left: auto; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .graph-physics-btn { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - color: var(--color-text-secondary); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + color: var(--fs-text-secondary); font-size: 0.875rem; font-family: inherit; padding: 0.3rem 0.7rem; @@ -659,12 +659,12 @@ onUnmounted(() => { transition: border-color 0.15s, color 0.15s; } .graph-physics-btn:hover { - border-color: var(--color-primary); - color: var(--color-text); + border-color: var(--fs-accent); + color: var(--fs-text-primary); } .graph-physics-btn.active { - border-color: var(--color-primary); - color: var(--color-primary); + border-color: var(--fs-accent); + color: var(--fs-accent); } .graph-physics-panel { @@ -672,8 +672,8 @@ onUnmounted(() => { flex-wrap: wrap; gap: 0.5rem 1.5rem; padding: 0.6rem 1rem; - background: var(--color-bg-secondary); - border-bottom: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border-bottom: 1px solid var(--fs-border-color); flex-shrink: 0; } @@ -688,20 +688,20 @@ onUnmounted(() => { .knob-label { font-size: 0.75rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); display: flex; justify-content: space-between; } .knob-label em { font-style: normal; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-variant-numeric: tabular-nums; } .physics-knob input[type="range"] { width: 100%; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); cursor: pointer; } @@ -709,7 +709,7 @@ onUnmounted(() => { flex: 1; position: relative; overflow: hidden; - background: var(--color-bg); + background: var(--fs-surface-page); } .graph-svg { @@ -728,15 +728,15 @@ onUnmounted(() => { } .graph-empty p { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.95rem; } .spinner { width: 28px; height: 28px; - border: 3px solid var(--color-border); - border-top-color: var(--color-primary); + border: 3px solid var(--fs-border-color); + border-top-color: var(--fs-accent); border-radius: 50%; animation: spin 0.7s linear infinite; } @@ -747,9 +747,9 @@ onUnmounted(() => { .graph-tooltip { position: absolute; - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); box-shadow: 0 4px 16px var(--color-shadow); padding: 0.5rem 0.75rem; pointer-events: none; @@ -760,7 +760,7 @@ onUnmounted(() => { .tooltip-title { font-size: 0.875rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); margin-bottom: 0.25rem; } @@ -773,15 +773,15 @@ onUnmounted(() => { .tag-chip { font-size: 0.7rem; - background: color-mix(in srgb, var(--color-primary) 15%, transparent); - color: var(--color-primary); + background: color-mix(in srgb, var(--fs-accent) 15%, transparent); + color: var(--fs-accent); border-radius: 999px; padding: 0.1rem 0.4rem; } .tooltip-meta { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); text-transform: capitalize; } @@ -792,8 +792,8 @@ onUnmounted(() => { right: 0; bottom: 0; width: 340px; - background: var(--color-bg-card); - border-left: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border-left: 1px solid var(--fs-border-color); box-shadow: -4px 0 16px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; @@ -815,7 +815,7 @@ onUnmounted(() => { align-items: center; gap: 0.5rem; padding: 0.6rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); flex-shrink: 0; } @@ -826,10 +826,10 @@ onUnmounted(() => { letter-spacing: 0.05em; padding: 0.15rem 0.45rem; border-radius: 10px; - border: 1px solid var(--color-border); - color: var(--color-text-muted); + border: 1px solid var(--fs-border-color); + color: var(--fs-text-tertiary); } -.peek-type-task { border-color: var(--color-primary); color: var(--color-primary); } +.peek-type-task { border-color: var(--fs-accent); color: var(--fs-accent); } .peek-actions { display: flex; @@ -840,7 +840,7 @@ onUnmounted(() => { .peek-link { font-size: 0.78rem; - color: var(--color-primary); + color: var(--fs-accent); text-decoration: none; } .peek-link:hover { text-decoration: underline; } @@ -848,20 +848,20 @@ onUnmounted(() => { .peek-close { background: none; border: none; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; cursor: pointer; padding: 0.1rem 0.25rem; border-radius: 3px; } -.peek-close:hover { color: var(--color-text); } +.peek-close:hover { color: var(--fs-text-primary); } .peek-title { margin: 0; padding: 0.75rem 0.75rem 0.4rem; font-size: 1rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); flex-shrink: 0; } @@ -877,7 +877,7 @@ onUnmounted(() => { flex: 1; overflow-y: auto; padding: 0 0.75rem 0.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .peek-prose { @@ -887,7 +887,7 @@ onUnmounted(() => { .peek-loading, .peek-empty { font-size: 0.82rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding-top: 0.5rem; } @@ -901,7 +901,7 @@ onUnmounted(() => { font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.4rem; } @@ -921,14 +921,14 @@ onUnmounted(() => { align-items: center; gap: 0.4rem; font-size: 0.82rem; - color: var(--color-text); + color: var(--fs-text-primary); cursor: pointer; padding: 0.25rem 0.4rem; border-radius: 4px; } .peek-linked-item:hover { - background: color-mix(in srgb, var(--color-primary) 8%, var(--color-bg-card)); - color: var(--color-primary); + background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-raised)); + color: var(--fs-accent); } .peek-linked-type { @@ -937,12 +937,12 @@ onUnmounted(() => { width: 1.1rem; height: 1.1rem; border-radius: 50%; - background: var(--color-bg); - border: 1px solid var(--color-border); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); display: flex; align-items: center; justify-content: center; flex-shrink: 0; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } diff --git a/frontend/src/views/KnowledgeView.vue b/frontend/src/views/KnowledgeView.vue index c1c80cc..b954d75 100644 --- a/frontend/src/views/KnowledgeView.vue +++ b/frontend/src/views/KnowledgeView.vue @@ -579,7 +579,7 @@ onUnmounted(() => { .knowledge-root { display: flex; flex-direction: column; - height: calc(100vh - var(--header-height)); + height: calc(100vh - var(--fs-layout-header)); overflow: hidden; } @@ -590,8 +590,8 @@ onUnmounted(() => { justify-content: space-between; gap: 12px; padding: 8px 20px; - background: var(--color-bg-secondary); - border-bottom: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border-bottom: 1px solid var(--fs-border-color); flex-shrink: 0; font-size: 0.82rem; flex-wrap: wrap; @@ -607,7 +607,7 @@ onUnmounted(() => { font-size: 0.78rem; } .today-link { - color: var(--color-primary); + color: var(--fs-accent); text-decoration: none; font-weight: 500; opacity: 0.85; @@ -625,19 +625,19 @@ onUnmounted(() => { /* ── Filter panel ────────────────────────────────────────── */ .filter-panel { - width: var(--sidebar-width); + width: var(--fs-layout-sidebar); flex-shrink: 0; padding: 16px 12px; - border-right: 1px solid var(--color-border); + border-right: 1px solid var(--fs-border-color); overflow-y: auto; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .filter-section { margin-bottom: 20px; } .filter-section + .filter-section::before { content: '· · ·'; display: block; text-align: center; - color: color-mix(in srgb, var(--color-primary) 30%, transparent); + color: color-mix(in srgb, var(--fs-accent) 30%, transparent); font-size: 0.9rem; letter-spacing: 0.4em; padding: 4px 0 12px; @@ -645,7 +645,7 @@ onUnmounted(() => { .filter-label { font-family: 'Fraunces', Georgia, serif; font-size: 0.95rem; - color: var(--color-primary); + color: var(--fs-accent); margin-bottom: 8px; padding: 0 4px; } @@ -662,14 +662,14 @@ onUnmounted(() => { padding: 8px 12px; border-radius: 10px; border: none; - background: var(--gradient-cta); + background: var(--fs-gradient-cta); color: var(--fs-text-on-action); cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: box-shadow 0.15s; } -.btn-new-note:hover { box-shadow: var(--glow-cta-hover); } +.btn-new-note:hover { box-shadow: var(--fs-glow-cta-hover); } .btn-new-icon { font-size: 1.1rem; line-height: 1; @@ -680,8 +680,8 @@ onUnmounted(() => { top: calc(100% + 6px); left: 0; right: 0; - background: var(--color-bg-card); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 10px; overflow: hidden; z-index: 50; @@ -696,15 +696,15 @@ onUnmounted(() => { padding: 9px 14px; background: none; border: none; - color: var(--color-text); + color: var(--fs-text-primary); cursor: pointer; font-size: 0.84rem; text-align: left; transition: background 0.12s, color 0.12s; } .new-note-menu button:hover { - background: var(--color-primary-tint); - color: var(--color-primary); + background: var(--fs-accent-soft); + color: var(--fs-accent); } .new-note-menu button svg { flex-shrink: 0; @@ -712,7 +712,7 @@ onUnmounted(() => { } .new-note-menu button:hover svg { opacity: 1; - stroke: var(--color-primary); + stroke: var(--fs-accent); } .filter-btn { @@ -725,7 +725,7 @@ onUnmounted(() => { border-radius: 7px; border: none; background: transparent; - color: var(--color-text); + color: var(--fs-text-primary); cursor: pointer; font-size: 0.85rem; margin-bottom: 2px; @@ -734,8 +734,8 @@ onUnmounted(() => { } .filter-btn:hover { background: rgba(255,255,255,0.05); opacity: 1; } .filter-btn.active { - background: var(--color-primary-wash); - color: var(--color-primary); + background: var(--fs-accent-wash); + color: var(--fs-accent); opacity: 1; } .filter-btn-label { flex: 1; } @@ -744,15 +744,15 @@ onUnmounted(() => { padding: 1px 6px; border-radius: 10px; background: rgba(255,255,255,0.07); - color: var(--color-muted); + color: var(--fs-text-tertiary); font-weight: 500; min-width: 20px; text-align: center; flex-shrink: 0; } .filter-btn.active .filter-count { - background: color-mix(in srgb, var(--color-primary) 20%, transparent); - color: var(--color-primary); + background: color-mix(in srgb, var(--fs-accent) 20%, transparent); + color: var(--fs-accent); } .filter-tag { font-size: 0.78rem; } @@ -772,7 +772,7 @@ onUnmounted(() => { gap: 10px; padding: 12px 20px; flex-shrink: 0; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .search-wrap { flex: 1; @@ -783,27 +783,27 @@ onUnmounted(() => { left: 10px; top: 50%; transform: translateY(-50%); - color: var(--color-muted); + color: var(--fs-text-tertiary); pointer-events: none; } .search-input { width: 100%; padding: 7px 12px 7px 32px; border-radius: 8px; - border: 1px solid var(--color-border); - background: var(--color-bg-tertiary); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); font-size: 0.88rem; outline: none; transition: border-color 0.15s; } -.search-input:focus { border-color: var(--color-primary); } +.search-input:focus { border-color: var(--fs-accent); } .sort-select { padding: 7px 10px; border-radius: 8px; - border: 1px solid var(--color-border); - background: var(--color-bg-tertiary); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); font-size: 0.85rem; cursor: pointer; outline: none; @@ -824,9 +824,9 @@ onUnmounted(() => { .k-card { position: relative; - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-xl); padding: 14px; cursor: pointer; transition: border-color 0.15s, transform 0.12s, box-shadow 0.15s; @@ -838,12 +838,12 @@ onUnmounted(() => { } .k-card:hover { transform: translateY(-2px); - box-shadow: 0 8px 28px color-mix(in srgb, var(--color-primary) 25%, transparent), 0 2px 8px rgba(0, 0, 0, 0.3); - border-color: color-mix(in srgb, var(--color-primary) 35%, transparent); + box-shadow: 0 8px 28px color-mix(in srgb, var(--fs-accent) 25%, transparent), 0 2px 8px rgba(0, 0, 0, 0.3); + border-color: color-mix(in srgb, var(--fs-accent) 35%, transparent); } /* Type-specific card DNA */ -.k-card--note { border-color: color-mix(in srgb, var(--color-primary) 20%, transparent); } +.k-card--note { border-color: color-mix(in srgb, var(--fs-accent) 20%, transparent); } .k-card--task { border-color: rgba(212, 160, 23, 0.18); } /* Top gradient bars */ @@ -858,7 +858,7 @@ onUnmounted(() => { } .k-card--note::before { right: 0; - background: linear-gradient(90deg, var(--color-primary), #7A6DA8); + background: linear-gradient(90deg, var(--fs-accent), #7A6DA8); } .k-card--task::before { right: 0; @@ -877,7 +877,7 @@ onUnmounted(() => { text-transform: uppercase; letter-spacing: 0.04em; } -.badge--note { background: color-mix(in srgb, var(--color-primary) 15%, transparent); color: #7A6DA8; } +.badge--note { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: #7A6DA8; } .badge--task { background: rgba(212,160,23,0.15); color: #fbbf24; } .badge--plan { background: rgba(99,102,241,0.18); color: #818cf8; } @@ -894,7 +894,7 @@ onUnmounted(() => { } .k-card-snippet { font-size: 0.8rem; - color: var(--color-muted); + color: var(--fs-text-tertiary); display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; @@ -914,9 +914,9 @@ onUnmounted(() => { padding: 1px 6px; border-radius: 8px; background: rgba(255,255,255,0.05); - color: var(--color-muted); + color: var(--fs-text-tertiary); } -.k-card-date { font-size: 0.72rem; color: var(--color-text-secondary); white-space: nowrap; opacity: 0.7; } +.k-card-date { font-size: 0.72rem; color: var(--fs-text-secondary); white-space: nowrap; opacity: 0.7; } /* Only rendered for a record another user owns, so an unmarked card is unambiguously the viewer's own. */ .shared-tag { @@ -924,8 +924,8 @@ onUnmounted(() => { padding: 0.08rem 0.35rem; border-radius: 4px; white-space: nowrap; - background: color-mix(in srgb, var(--color-text-secondary) 15%, transparent); - color: var(--color-text-secondary); + background: color-mix(in srgb, var(--fs-text-secondary) 15%, transparent); + color: var(--fs-text-secondary); } /* ── Task card ──────────────────────────────────────────── */ @@ -945,10 +945,10 @@ onUnmounted(() => { border-radius: 8px; font-weight: 500; } -.status--todo { background: var(--color-status-todo-bg); color: var(--color-status-todo); } -.status--in_progress { background: var(--color-status-in-progress-bg); color: var(--color-status-in-progress); } -.status--done { background: var(--color-status-done-bg); color: var(--color-status-done); } -.status--cancelled { background: var(--color-status-todo-bg); color: var(--color-status-todo); text-decoration: line-through; } +.status--todo { background: var(--fs-status-todo-bg); color: var(--fs-status-todo); } +.status--in_progress { background: var(--fs-status-in-progress-bg); color: var(--fs-status-in-progress); } +.status--done { background: var(--fs-status-done-bg); color: var(--fs-status-done); } +.status--cancelled { background: var(--fs-status-todo-bg); color: var(--fs-status-todo); text-decoration: line-through; } .priority-badge { font-size: 0.7rem; @@ -956,16 +956,16 @@ onUnmounted(() => { border-radius: 8px; font-weight: 500; } -.priority--low { background: var(--color-priority-low-bg); color: var(--color-priority-low); } -.priority--normal { background: var(--color-priority-medium-bg); color: var(--color-priority-medium); } -.priority--high { background: var(--color-priority-high-bg); color: var(--color-priority-high); } +.priority--low { background: var(--fs-priority-low-bg); color: var(--fs-priority-low); } +.priority--normal { background: var(--fs-priority-medium-bg); color: var(--fs-priority-medium); } +.priority--high { background: var(--fs-priority-high-bg); color: var(--fs-priority-high); } .task-due { font-size: 0.78rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .task-overdue { - color: var(--color-overdue); + color: var(--fs-overdue); font-weight: 500; } @@ -977,7 +977,7 @@ onUnmounted(() => { align-items: center; justify-content: center; padding: 60px 20px; - color: var(--color-muted); + color: var(--fs-text-tertiary); text-align: center; gap: 6px; } @@ -985,7 +985,7 @@ onUnmounted(() => { .empty-narrator { font-family: 'Fraunces', Georgia, serif; font-size: 1rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); opacity: 0.85; } @@ -999,17 +999,17 @@ onUnmounted(() => { } .sentinel-loading { font-size: 0.8rem; - color: var(--color-muted); + color: var(--fs-text-tertiary); } /* ── Graph panel ─────────────────────────────────────────── */ .graph-panel { width: 500px; flex-shrink: 0; - border-left: 1px solid var(--color-border); + border-left: 1px solid var(--fs-border-color); display: flex; flex-direction: column; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); transition: width 0.2s ease; } .graph-panel.expanded { @@ -1022,7 +1022,7 @@ onUnmounted(() => { padding: 10px 14px; font-size: 0.85rem; font-weight: 500; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); flex-shrink: 0; } /* RESTORED (#2444). The panel is a flex COLUMN and its header is @@ -1050,15 +1050,15 @@ onUnmounted(() => { .dup-panel { margin-bottom: 1.25rem; padding: 0.85rem 1rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 8px; - background: var(--color-surface-alt); + background: var(--fs-surface-hover); } .dup-empty, .dup-head { margin: 0 0 0.5rem; font-size: 0.85rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .dup-empty { margin-bottom: 0; } .dup-group { @@ -1067,7 +1067,7 @@ onUnmounted(() => { gap: 0.75rem; flex-wrap: wrap; padding: 0.5rem 0; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); } .dup-members { display: flex; @@ -1080,23 +1080,23 @@ onUnmounted(() => { font-size: 0.8rem; padding: 0.1rem 0.45rem; border-radius: 4px; - background: color-mix(in srgb, var(--color-text-muted) 12%, transparent); - color: var(--color-text); + background: color-mix(in srgb, var(--fs-text-tertiary) 12%, transparent); + color: var(--fs-text-primary); text-decoration: none; overflow-wrap: anywhere; } -.dup-member:hover { background: var(--color-hover); } +.dup-member:hover { background: var(--fs-surface-hover); } .dup-score { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; } /* A set someone already ruled on — quiet, not celebratory: it means "skip". */ .dup-claimed { font-size: 0.72rem; - color: var(--color-text-muted); - border: 1px solid var(--color-border); + color: var(--fs-text-tertiary); + border: 1px solid var(--fs-border-color); border-radius: 4px; padding: 0.05rem 0.4rem; white-space: nowrap; diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index cb396bb..76e2584 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -123,9 +123,9 @@ function loginWithOAuth() { .auth-card { width: 100%; max-width: 400px; - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 2rem; } .auth-brand { @@ -142,11 +142,11 @@ function loginWithOAuth() { .auth-hint { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin-bottom: 1rem; } .auth-hint a { - color: var(--color-primary); + color: var(--fs-accent); } .field { margin-bottom: 1rem; @@ -160,19 +160,19 @@ function loginWithOAuth() { .input { width: 100%; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; } .input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; margin: 0 0 0.75rem; } @@ -181,23 +181,23 @@ function loginWithOAuth() { align-items: center; gap: 0.75rem; margin: 1.25rem 0; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.85rem; } .divider::before, .divider::after { content: ""; flex: 1; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); } .auth-footer { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin: 1rem 0 0; } .auth-footer a { - color: var(--color-primary); + color: var(--fs-accent); } .forgot-link { text-align: right; @@ -205,9 +205,9 @@ function loginWithOAuth() { font-size: 0.85rem; } .forgot-link a { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .forgot-link a:hover { - color: var(--color-primary); + color: var(--fs-accent); } diff --git a/frontend/src/views/LogsView.vue b/frontend/src/views/LogsView.vue index 20007cd..fc11604 100644 --- a/frontend/src/views/LogsView.vue +++ b/frontend/src/views/LogsView.vue @@ -263,9 +263,9 @@ function clearFilters() { margin: 0 0 1.5rem; } .settings-section { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; } @@ -292,23 +292,23 @@ function clearFilters() { .stat-count { font-size: 1.5rem; font-weight: 700; - color: var(--color-text); + color: var(--fs-text-primary); } .stat-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .stat-audit { - color: var(--color-primary); + color: var(--fs-accent); } .stat-usage { - color: var(--color-success); + color: var(--fs-success); } .stat-error { - color: var(--color-danger); + color: var(--fs-error); } /* Filters */ @@ -321,10 +321,10 @@ function clearFilters() { .filter-input, .filter-date { padding: 0.4rem 0.6rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); + color: var(--fs-text-primary); font-size: 0.85rem; } .filter-select { @@ -342,7 +342,7 @@ function clearFilters() { .loading-msg, .empty-msg { text-align: center; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.9rem; padding: 1rem 0; } @@ -356,13 +356,13 @@ function clearFilters() { font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .logs-table td { padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.85rem; } .logs-table tbody tr:last-child td { @@ -373,18 +373,18 @@ function clearFilters() { transition: background 0.1s; } .log-row:hover { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .row-expanded { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .cell-time { white-space: nowrap; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; } .cell-user { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .cell-action { max-width: 280px; @@ -399,22 +399,22 @@ function clearFilters() { .cell-ip { font-family: monospace; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); white-space: nowrap; } .cell-duration { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; white-space: nowrap; } .detail-ip { font-family: monospace; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.4rem; } .text-error { - color: var(--color-danger); + color: var(--fs-error); } /* Category badges */ @@ -425,19 +425,19 @@ function clearFilters() { text-transform: uppercase; letter-spacing: 0.05em; padding: 0.1rem 0.35rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); } .cat-audit { - color: var(--color-primary); - background: color-mix(in srgb, var(--color-primary) 15%, transparent); + color: var(--fs-accent); + background: color-mix(in srgb, var(--fs-accent) 15%, transparent); } .cat-usage { - color: var(--color-success); - background: color-mix(in srgb, var(--color-success) 15%, transparent); + color: var(--fs-success); + background: color-mix(in srgb, var(--fs-success) 15%, transparent); } .cat-error { - color: var(--color-danger); - background: color-mix(in srgb, var(--color-danger) 15%, transparent); + color: var(--fs-error); + background: color-mix(in srgb, var(--fs-error) 15%, transparent); } /* Method tag */ @@ -448,8 +448,8 @@ function clearFilters() { font-family: monospace; padding: 0.05rem 0.25rem; border-radius: 3px; - background: var(--color-bg-secondary); - color: var(--color-text-muted); + background: var(--fs-surface-raised); + color: var(--fs-text-tertiary); margin-right: 0.25rem; } @@ -458,14 +458,14 @@ function clearFilters() { cells don't carry, and the row exists to scope the rule below (#2444). */ .detail-row td { padding: 0 0.75rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .detail-json { margin: 0; padding: 0.75rem; - background: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.8rem; overflow-x: auto; white-space: pre-wrap; diff --git a/frontend/src/views/NoteEditorView.vue b/frontend/src/views/NoteEditorView.vue index 0698d2f..86ee526 100644 --- a/frontend/src/views/NoteEditorView.vue +++ b/frontend/src/views/NoteEditorView.vue @@ -633,13 +633,13 @@ onUnmounted(() => assist.clearSelection()); gap: 0.75rem; flex-wrap: wrap; padding-bottom: 0.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .editor-tabs { display: inline-flex; - background: var(--color-bg); - border: 1px solid var(--color-border); + background: var(--fs-surface-page); + border: 1px solid var(--fs-border-color); border-radius: 8px; padding: 2px; gap: 2px; @@ -653,14 +653,14 @@ onUnmounted(() => assist.clearSelection()); padding: 0.22rem 0.75rem; font-size: 0.78rem; font-weight: 500; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); cursor: pointer; transition: background 0.15s, color 0.15s; } -.tab:hover { color: var(--color-text); } +.tab:hover { color: var(--fs-text-primary); } .tab.active { - background: var(--color-surface); - color: var(--color-text); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); box-shadow: 0 1px 3px rgba(0,0,0,0.12); } @@ -679,14 +679,14 @@ onUnmounted(() => assist.clearSelection()); .stream-label { font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .stream-preview { - border: 1px solid var(--color-input-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); padding: 0.75rem; - background: var(--color-bg-card); + background: var(--fs-surface-raised); min-height: 200px; } @@ -699,7 +699,7 @@ onUnmounted(() => assist.clearSelection()); .note-sidebar { width: 280px; flex-shrink: 0; - border-left: 1px solid var(--color-border); + border-left: 1px solid var(--fs-border-color); overflow-y: auto; display: flex; flex-direction: column; @@ -708,17 +708,17 @@ onUnmounted(() => assist.clearSelection()); .sb-select, .sb-input { width: 100%; padding: 5px 8px; - border-radius: var(--radius-sm); - border: 1px solid var(--color-input-border); - background: var(--color-bg-tertiary); - color: var(--color-text); + border-radius: var(--fs-radius-sm); + border: 1px solid var(--fs-border-color); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); font-size: 0.82rem; font-family: inherit; outline: none; transition: border-color 0.15s; } .sb-select:focus, .sb-input:focus { - border-color: var(--color-primary); + border-color: var(--fs-accent); } /* Tag suggest row inside sidebar */ @@ -742,13 +742,13 @@ onUnmounted(() => assist.clearSelection()); font-size: 0.72rem; padding: 0.15rem 0.5rem; background: none; - border: 1px solid var(--color-primary); - border-radius: var(--radius-sm); - color: var(--color-primary); + border: 1px solid var(--fs-accent); + border-radius: var(--fs-radius-sm); + color: var(--fs-accent); cursor: pointer; font-family: inherit; } -.btn-link-all:hover { background: var(--color-action-primary); color: var(--fs-text-on-action); } +.btn-link-all:hover { background: var(--fs-action-primary); color: var(--fs-text-on-action); } .link-suggest-list { display: flex; @@ -766,14 +766,14 @@ onUnmounted(() => assist.clearSelection()); .link-suggest-title { flex: 1; font-family: monospace; - color: var(--color-primary); + color: var(--fs-accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .link-suggest-count { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.72rem; flex-shrink: 0; } @@ -783,13 +783,13 @@ onUnmounted(() => assist.clearSelection()); font-size: 0.72rem; padding: 0.1rem 0.4rem; background: none; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); cursor: pointer; font-family: inherit; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } -.btn-apply-link:hover { border-color: var(--color-primary); color: var(--color-primary); } +.btn-apply-link:hover { border-color: var(--fs-accent); color: var(--fs-accent); } /* Writing Assistant section */ .assist-section { @@ -801,7 +801,7 @@ onUnmounted(() => assist.clearSelection()); .assist-section-title { font-size: 0.78rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); text-transform: uppercase; letter-spacing: 0.05em; } @@ -810,19 +810,19 @@ onUnmounted(() => assist.clearSelection()); .ef-label { font-family: 'Fraunces', Georgia, serif; font-size: 0.92rem; - color: var(--color-primary); + color: var(--fs-accent); } .prompt-editor { width: 100%; min-height: 60vh; margin-top: 8px; padding: 14px 16px; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 8px; - background: var(--color-surface); - color: var(--color-text); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); /* Prompts are plain markdown — a code-style editor, not rich text. */ - font-family: var(--font-mono); + font-family: var(--fs-font-mono); font-size: 0.88rem; line-height: 1.55; tab-size: 2; @@ -830,7 +830,7 @@ onUnmounted(() => assist.clearSelection()); outline: none; } .prompt-editor:focus { - border-color: var(--color-primary); + border-color: var(--fs-accent); } /* Narrow screen: sidebar collapses */ @media (max-width: 720px) { @@ -839,7 +839,7 @@ onUnmounted(() => assist.clearSelection()); .note-sidebar { width: 100%; border-left: none; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); overflow-y: visible; } } diff --git a/frontend/src/views/NoteViewerView.vue b/frontend/src/views/NoteViewerView.vue index 7e89d34..9c09f32 100644 --- a/frontend/src/views/NoteViewerView.vue +++ b/frontend/src/views/NoteViewerView.vue @@ -340,7 +340,7 @@ async function convertToTask() { gap: 0.5rem; flex-wrap: wrap; font-size: 0.83rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.75rem; } .meta-item { @@ -359,7 +359,7 @@ async function convertToTask() { } .backlinks { margin-top: 2.5rem; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); padding-top: 1.25rem; } .backlinks-heading { @@ -370,14 +370,14 @@ async function convertToTask() { font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.75rem; } .backlinks-count { margin-left: 0.2rem; font-size: 0.72rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 999px; padding: 0 0.4rem; line-height: 1.4; @@ -392,18 +392,18 @@ async function convertToTask() { align-items: center; gap: 0.6rem; padding: 0.5rem 0.75rem; - border-radius: var(--radius-md); - background: var(--color-bg-card); - border: 1px solid var(--color-border); + border-radius: var(--fs-radius-lg); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); text-decoration: none; - color: var(--color-text); + color: var(--fs-text-primary); transition: border-color 0.15s, box-shadow 0.15s; font-size: 0.9rem; } .backlink-card:hover { - border-color: color-mix(in srgb, var(--color-primary) 50%, transparent); + border-color: color-mix(in srgb, var(--fs-accent) 50%, transparent); box-shadow: 0 2px 8px rgba(0,0,0,0.06); - color: var(--color-primary); + color: var(--fs-accent); } .backlink-type-badge { font-size: 0.68rem; @@ -415,9 +415,9 @@ async function convertToTask() { flex-shrink: 0; } .badge-note { - background: color-mix(in srgb, var(--color-primary) 12%, transparent); - color: var(--color-primary); - border: 1px solid color-mix(in srgb, var(--color-primary) 25%, transparent); + background: color-mix(in srgb, var(--fs-accent) 12%, transparent); + color: var(--fs-accent); + border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent); } .badge-task { background: color-mix(in srgb, #f59e0b 12%, transparent); @@ -446,12 +446,12 @@ async function convertToTask() { .skel-title, .skel-meta, .skel-line { - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); background: linear-gradient( 90deg, - var(--color-bg-secondary) 25%, - color-mix(in srgb, var(--color-text-muted) 18%, var(--color-bg-secondary)) 50%, - var(--color-bg-secondary) 75% + var(--fs-surface-raised) 25%, + color-mix(in srgb, var(--fs-text-tertiary) 18%, var(--fs-surface-raised)) 50%, + var(--fs-surface-raised) 75% ); background-size: 200% 100%; animation: skel-shine 1.5s ease infinite; @@ -463,7 +463,7 @@ async function convertToTask() { } .skel-btn { width: 70px; height: 32px; } .skel-btn--wide { width: 90px; } -.skel-title { height: 2.2rem; width: 70%; border-radius: var(--radius-md); } +.skel-title { height: 2.2rem; width: 70%; border-radius: var(--fs-radius-lg); } .skel-meta { height: 0.85rem; width: 40%; } .skel-line { height: 0.9rem; } .skel-line--short { width: 55%; } diff --git a/frontend/src/views/ProjectListView.vue b/frontend/src/views/ProjectListView.vue index 89b851d..3ecdf16 100644 --- a/frontend/src/views/ProjectListView.vue +++ b/frontend/src/views/ProjectListView.vue @@ -317,9 +317,9 @@ function overallPct(project: Project): { total: number; pct: number } { diff --git a/frontend/src/views/RegisterView.vue b/frontend/src/views/RegisterView.vue index 6c0d42f..d7ef192 100644 --- a/frontend/src/views/RegisterView.vue +++ b/frontend/src/views/RegisterView.vue @@ -141,9 +141,9 @@ async function handleSubmit() { .auth-card { width: 100%; max-width: 400px; - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 2rem; } .auth-brand { @@ -159,13 +159,13 @@ async function handleSubmit() { } .loading-msg { text-align: center; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.9rem; padding: 1rem 0; } .closed-msg { text-align: center; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.95rem; padding: 0.5rem 0; } @@ -184,45 +184,45 @@ async function handleSubmit() { .input { width: 100%; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; } .input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .input-error { - border-color: var(--color-danger); + border-color: var(--fs-error); } .input-error:focus { - border-color: var(--color-danger); + border-color: var(--fs-error); } .field-hint { margin: 0.35rem 0 0; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .error-hint { margin: 0.35rem 0 0; font-size: 0.8rem; - color: var(--color-danger); + color: var(--fs-error); } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; margin: 0 0 0.75rem; } .auth-footer { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin: 1rem 0 0; } .auth-footer a { - color: var(--color-primary); + color: var(--fs-accent); } diff --git a/frontend/src/views/ResetPasswordView.vue b/frontend/src/views/ResetPasswordView.vue index 51e6c03..1cfc2bb 100644 --- a/frontend/src/views/ResetPasswordView.vue +++ b/frontend/src/views/ResetPasswordView.vue @@ -117,9 +117,9 @@ async function handleSubmit() { .auth-card { width: 100%; max-width: 400px; - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 2rem; } .auth-brand { @@ -135,7 +135,7 @@ async function handleSubmit() { } .error-block { text-align: center; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.95rem; padding: 0.5rem 0; } @@ -144,7 +144,7 @@ async function handleSubmit() { } .success-msg { text-align: center; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-size: 0.95rem; padding: 0.5rem 0; } @@ -163,45 +163,45 @@ async function handleSubmit() { .input { width: 100%; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; } .input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .input-error { - border-color: var(--color-danger); + border-color: var(--fs-error); } .input-error:focus { - border-color: var(--color-danger); + border-color: var(--fs-error); } .field-hint { margin: 0.35rem 0 0; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .error-hint { margin: 0.35rem 0 0; font-size: 0.8rem; - color: var(--color-danger); + color: var(--fs-error); } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; margin: 0 0 0.75rem; } .auth-footer { text-align: center; font-size: 0.9rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); margin: 1rem 0 0; } .auth-footer a { - color: var(--color-primary); + color: var(--fs-accent); } diff --git a/frontend/src/views/RulesView.vue b/frontend/src/views/RulesView.vue index 459b619..1236267 100644 --- a/frontend/src/views/RulesView.vue +++ b/frontend/src/views/RulesView.vue @@ -107,10 +107,10 @@ watch(() => route.query, syncFromRoute); grid-template-columns: 280px 300px 1fr; height: 100vh; gap: 1px; - background: var(--color-border); + background: var(--fs-border-color); } .pane.empty { - background: var(--color-surface); + background: var(--fs-surface-hover); padding: 1rem; opacity: 0.6; font-style: italic; diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 96a7b9f..e8ffb56 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -2353,7 +2353,7 @@ function formatUserDate(iso: string): string { font-weight: 500; text-transform: uppercase; letter-spacing: 0.07em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 0.25rem 0.75rem 0.2rem; } .sidebar-item { @@ -2366,19 +2366,19 @@ function formatUserDate(iso: string): string { background: none; cursor: pointer; font-size: 0.875rem; - color: var(--color-text-secondary); - border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + color: var(--fs-text-secondary); + border-radius: 0 var(--fs-radius-sm) var(--fs-radius-sm) 0; transition: color 0.15s, background 0.15s, border-color 0.15s; font-family: inherit; } .sidebar-item:hover { - color: var(--color-text); - background: var(--color-bg-secondary); + color: var(--fs-text-primary); + background: var(--fs-surface-raised); } .sidebar-item.active { - color: var(--color-primary); - background: color-mix(in srgb, var(--color-primary) 8%, transparent); - border-left-color: var(--color-primary); + color: var(--fs-accent); + background: color-mix(in srgb, var(--fs-accent) 8%, transparent); + border-left-color: var(--fs-accent); font-weight: 500; } @@ -2396,9 +2396,9 @@ function formatUserDate(iso: string): string { align-items: start; } .settings-section { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1.25rem; } .settings-section.full-width { @@ -2410,12 +2410,12 @@ function formatUserDate(iso: string): string { font-weight: 500; text-transform: uppercase; letter-spacing: 0.07em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .section-desc { margin: 0 0 1rem; font-size: 0.875rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); line-height: 1.5; } @@ -2429,8 +2429,8 @@ function formatUserDate(iso: string): string { .model-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.45rem 0.6rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 6px; } .model-row-info { display: flex; align-items: center; gap: 0.4rem; min-width: 0; flex: 1; } @@ -2439,44 +2439,44 @@ function formatUserDate(iso: string): string { font-size: 0.68rem; padding: 0.1rem 0.4rem; border-radius: 3px; font-weight: 500; white-space: nowrap; } .model-badge--loaded { background: color-mix(in srgb, #22c55e 18%, transparent); color: #22c55e; } -.model-badge--default { background: color-mix(in srgb, var(--color-primary) 18%, transparent); color: var(--color-primary); } +.model-badge--default { background: color-mix(in srgb, var(--fs-accent) 18%, transparent); color: var(--fs-accent); } .model-row-right { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; } -.model-size { font-size: 0.78rem; color: var(--color-text-muted); } +.model-size { font-size: 0.78rem; color: var(--fs-text-tertiary); } .model-delete-btn { - background: none; border: none; cursor: pointer; color: var(--color-text-muted); + background: none; border: none; cursor: pointer; color: var(--fs-text-tertiary); font-size: 0.8rem; padding: 0.2rem 0.35rem; border-radius: 3px; line-height: 1; transition: color 0.15s, background 0.15s; } -.model-delete-btn:hover:not(:disabled) { color: var(--color-action-destructive); background: color-mix(in srgb, var(--color-action-destructive) 10%, transparent); } +.model-delete-btn:hover:not(:disabled) { color: var(--fs-action-destructive); background: color-mix(in srgb, var(--fs-action-destructive) 10%, transparent); } .model-delete-btn:disabled { opacity: 0.4; cursor: default; } .model-pull-form { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .model-pull-form .input { flex: 1; } .model-suggestions { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.4rem; } -.suggestions-label { font-size: 0.75rem; color: var(--color-text-muted); white-space: nowrap; } +.suggestions-label { font-size: 0.75rem; color: var(--fs-text-tertiary); white-space: nowrap; } .suggestion-chip { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: 4px; - border: 1px solid var(--color-border); background: var(--color-bg-card); - cursor: pointer; font-family: monospace; color: var(--color-text); + border: 1px solid var(--fs-border-color); background: var(--fs-surface-raised); + cursor: pointer; font-family: monospace; color: var(--fs-text-primary); transition: border-color 0.12s, background 0.12s; } -.suggestion-chip:hover:not(:disabled) { border-color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 8%, var(--color-bg-card)); } +.suggestion-chip:hover:not(:disabled) { border-color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-raised)); } .suggestion-chip:disabled { opacity: 0.4; cursor: default; } .model-pull-progress { margin-top: 0.6rem; } -.pull-status { font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: 0.25rem; } +.pull-status { font-size: 0.8rem; color: var(--fs-text-tertiary); margin-bottom: 0.25rem; } .pull-bar-track { - height: 4px; background: var(--color-border); border-radius: 2px; overflow: hidden; + height: 4px; background: var(--fs-border-color); border-radius: 2px; overflow: hidden; } .pull-bar-fill { - height: 100%; background: var(--color-primary); border-radius: 2px; + height: 100%; background: var(--fs-accent); border-radius: 2px; transition: width 0.3s ease; } .pull-bar-indeterminate { - height: 4px; background: var(--color-border); border-radius: 2px; + height: 4px; background: var(--fs-border-color); border-radius: 2px; position: relative; overflow: hidden; } .pull-bar-indeterminate::after { content: ""; position: absolute; top: 0; left: -40%; - width: 40%; height: 100%; background: var(--color-primary); border-radius: 2px; + width: 40%; height: 100%; background: var(--fs-accent); border-radius: 2px; animation: indeterminate 1.2s ease-in-out infinite; } @keyframes indeterminate { @@ -2503,27 +2503,27 @@ function formatUserDate(iso: string): string { font-size: 0.875rem; font-weight: 500; margin-bottom: 0.35rem; - color: var(--color-text); + color: var(--fs-text-primary); } .input { width: 100%; padding: 0.45rem 0.7rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.9rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; font-family: inherit; } .input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .field-hint { margin: 0.3rem 0 0; font-size: 0.78rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .field-hint-warn { display: block; @@ -2552,12 +2552,12 @@ function formatUserDate(iso: string): string { font-size: 0.82rem; padding: 0.2rem 0; } -.db-maint-table-list .dm-status { color: var(--color-text-muted); } -.db-maint-table-list li.dm-failed .dm-status { color: var(--color-danger); } +.db-maint-table-list .dm-status { color: var(--fs-text-tertiary); } +.db-maint-table-list li.dm-failed .dm-status { color: var(--fs-error); } /* DB table-health readout */ .db-health { margin-top: 1.5rem; } -.db-health-total { color: var(--color-text-muted); font-weight: 400; } +.db-health-total { color: var(--fs-text-tertiary); font-weight: 400; } .db-health-scroll { overflow-x: auto; margin-top: 0.5rem; } .db-health-table { width: 100%; @@ -2567,35 +2567,35 @@ function formatUserDate(iso: string): string { .db-health-table th, .db-health-table td { text-align: left; padding: 0.35rem 0.6rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); white-space: nowrap; } .db-health-table th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-weight: 500; } .db-health-table td.num, .db-health-table th.num { text-align: right; font-variant-numeric: tabular-nums; } -.db-health-table tr.dh-warn td { color: var(--color-warning); } -.db-health-table tr.dh-warn td:first-child code { color: var(--color-warning); } +.db-health-table tr.dh-warn td { color: var(--fs-warning); } +.db-health-table tr.dh-warn td:first-child code { color: var(--fs-warning); } .btn-warn:hover:not(:disabled) { - background: var(--color-warning); + background: var(--fs-warning); color: var(--fs-text-on-action); } .saved-msg { - color: var(--color-success); + color: var(--fs-success); font-size: 0.875rem; font-weight: 500; } -.input-error { border-color: var(--color-danger); } -.input-error:focus { border-color: var(--color-danger); } +.input-error { border-color: var(--fs-error); } +.input-error:focus { border-color: var(--fs-error); } .error-hint { margin: 0.3rem 0 0; font-size: 0.78rem; - color: var(--color-danger); + color: var(--fs-error); } .retention-row { @@ -2623,21 +2623,21 @@ function formatUserDate(iso: string): string { align-items: center; gap: 0.5rem; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); cursor: pointer; } .checkbox-field input[type="checkbox"] { width: 16px; height: 16px; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); } /* Search test */ .url-chip { font-size: 0.8rem; padding: 0.1rem 0.4rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 4px; } .not-configured { @@ -2652,7 +2652,7 @@ function formatUserDate(iso: string): string { .search-row .input { flex: 1; } .search-error { font-size: 0.875rem; - color: var(--color-danger); + color: var(--fs-error); margin: 0 0 0.5rem; } .search-results { @@ -2665,9 +2665,9 @@ function formatUserDate(iso: string): string { } .search-result { padding: 0.65rem 0.85rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); } .result-header { display: flex; @@ -2679,20 +2679,20 @@ function formatUserDate(iso: string): string { .result-title { font-size: 0.9rem; font-weight: 500; - color: var(--color-primary); + color: var(--fs-accent); text-decoration: none; word-break: break-word; } .result-title:hover { text-decoration: underline; } .result-host { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); white-space: nowrap; } .result-snippet { margin: 0; font-size: 0.82rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; @@ -2721,10 +2721,10 @@ function formatUserDate(iso: string): string { margin: 0 0 0.5rem; font-size: 0.875rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .test-email-section { - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); padding-top: 1rem; } .test-email-row { @@ -2737,7 +2737,7 @@ function formatUserDate(iso: string): string { /* Push notifications */ .push-unsupported { font-size: 0.875rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .push-status-row { display: flex; @@ -2747,7 +2747,7 @@ function formatUserDate(iso: string): string { font-size: 0.875rem; } .push-status-label { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); font-weight: 500; } .push-permission-badge, @@ -2758,15 +2758,15 @@ function formatUserDate(iso: string): string { letter-spacing: 0.04em; padding: 0.1rem 0.4rem; border-radius: 999px; - background: color-mix(in srgb, var(--color-text-muted) 15%, transparent); - color: var(--color-text-muted); + background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); + color: var(--fs-text-tertiary); } -.perm-granted { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); } -.perm-denied { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); } -.sub-active { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); } +.perm-granted { background: color-mix(in srgb, var(--fs-success) 15%, transparent); color: var(--fs-success); } +.perm-denied { background: color-mix(in srgb, var(--fs-error) 15%, transparent); color: var(--fs-error); } +.sub-active { background: color-mix(in srgb, var(--fs-success) 15%, transparent); color: var(--fs-success); } .push-error { font-size: 0.82rem; - color: var(--color-danger); + color: var(--fs-error); margin: 0.25rem 0 0; } @@ -2780,7 +2780,7 @@ function formatUserDate(iso: string): string { position: static; padding-right: 0; padding-bottom: 0.5rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); margin-bottom: 1rem; } .sidebar-group { @@ -2796,13 +2796,13 @@ function formatUserDate(iso: string): string { width: auto; border-left: none; border-bottom: 2px solid transparent; - border-radius: var(--radius-sm) var(--radius-sm) 0 0; + border-radius: var(--fs-radius-sm) var(--fs-radius-sm) 0 0; font-size: 0.82rem; padding: 0.35rem 0.7rem; } .sidebar-item.active { - border-bottom-color: var(--color-primary); - background: var(--color-primary-tint); + border-bottom-color: var(--fs-accent); + background: var(--fs-accent-soft); } .settings-grid { grid-template-columns: 1fr; @@ -2827,8 +2827,8 @@ function formatUserDate(iso: string): string { } .registration-info { flex: 1; } .registration-status { margin: 0; font-size: 0.95rem; } -.text-success { color: var(--color-success); } -.text-muted { color: var(--color-text-muted); } +.text-success { color: var(--fs-success); } +.text-muted { color: var(--fs-text-tertiary); } .invite-form { display: flex; gap: 0.5rem; @@ -2840,7 +2840,7 @@ function formatUserDate(iso: string): string { margin: 0 0 0.5rem; font-size: 0.85rem; font-weight: 500; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .users-table { width: 100%; border-collapse: collapse; } .users-table th { @@ -2849,19 +2849,19 @@ function formatUserDate(iso: string): string { font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .users-table td { padding: 0.65rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.9rem; } .users-table tbody tr:last-child td { border-bottom: none; } .cell-username { font-weight: 500; } -.cell-email { color: var(--color-text-secondary); } -.cell-date { color: var(--color-text-muted); font-size: 0.85rem; } +.cell-email { color: var(--fs-text-secondary); } +.cell-date { color: var(--fs-text-tertiary); font-size: 0.85rem; } .cell-actions { white-space: nowrap; } .role-badge { display: inline-block; @@ -2870,17 +2870,17 @@ function formatUserDate(iso: string): string { text-transform: uppercase; letter-spacing: 0.05em; padding: 0.15rem 0.4rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); } .role-admin { - color: var(--color-primary); - background: color-mix(in srgb, var(--color-primary) 15%, transparent); + color: var(--fs-accent); + background: color-mix(in srgb, var(--fs-accent) 15%, transparent); } .role-user { - color: var(--color-text-muted); - background: var(--color-bg-secondary); + color: var(--fs-text-tertiary); + background: var(--fs-surface-raised); } -.you-label { font-size: 0.8rem; color: var(--color-text-muted); } +.you-label { font-size: 0.8rem; color: var(--fs-text-tertiary); } /* Per-row delete (users / invitations / etc.): ghost → Oxblood on hover */ /* Logs panel */ @@ -2894,17 +2894,17 @@ function formatUserDate(iso: string): string { align-items: center; gap: 0.15rem; } -.stat-count { font-size: 1.5rem; font-weight: 500; color: var(--color-text); } +.stat-count { font-size: 1.5rem; font-weight: 500; color: var(--fs-text-primary); } .stat-label { font-size: 0.75rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } -.stat-audit { color: var(--color-primary); } -.stat-usage { color: var(--color-success); } -.stat-error { color: var(--color-danger); } +.stat-audit { color: var(--fs-accent); } +.stat-usage { color: var(--fs-success); } +.stat-error { color: var(--fs-error); } .filter-bar { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.75rem; } .filter-select { min-width: 140px; } @@ -2918,49 +2918,49 @@ function formatUserDate(iso: string): string { font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .logs-table td { padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.85rem; } .logs-table tbody tr:last-child td { border-bottom: none; } .log-row { cursor: pointer; transition: background 0.1s; } -.log-row:hover { background: var(--color-bg-secondary); } -.row-expanded { background: var(--color-bg-secondary); } -.cell-time { white-space: nowrap; color: var(--color-text-muted); font-size: 0.8rem; } -.cell-user { color: var(--color-text-secondary); } +.log-row:hover { background: var(--fs-surface-raised); } +.row-expanded { background: var(--fs-surface-raised); } +.cell-time { white-space: nowrap; color: var(--fs-text-tertiary); font-size: 0.8rem; } +.cell-user { color: var(--fs-text-secondary); } .cell-action { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .cell-status { font-family: monospace; font-size: 0.85rem; } -.cell-duration { color: var(--color-text-muted); font-size: 0.8rem; white-space: nowrap; } -.text-error { color: var(--color-danger); } +.cell-duration { color: var(--fs-text-tertiary); font-size: 0.8rem; white-space: nowrap; } +.text-error { color: var(--fs-error); } /* Bare by design, like LogsView's twin of this: a `` has nothing to style that its cells don't carry (#2444). */ -.detail-row td { padding: 0 0.75rem 0.75rem; border-bottom: 1px solid var(--color-border); } -.detail-ip { font-family: monospace; font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: 0.4rem; } +.detail-row td { padding: 0 0.75rem 0.75rem; border-bottom: 1px solid var(--fs-border-color); } +.detail-ip { font-family: monospace; font-size: 0.8rem; color: var(--fs-text-tertiary); margin-bottom: 0.4rem; } .detail-json { margin: 0; padding: 0.75rem; - background: var(--color-bg); border: 1px solid var(--color-border); - border-radius: var(--radius-sm); font-size: 0.8rem; + background: var(--fs-surface-page); border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.8rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 300px; } .category-badge { display: inline-block; font-size: 0.65rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; - padding: 0.1rem 0.35rem; border-radius: var(--radius-sm); + padding: 0.1rem 0.35rem; border-radius: var(--fs-radius-sm); } -.cat-audit { color: var(--color-primary); background: color-mix(in srgb, var(--color-primary) 15%, transparent); } -.cat-usage { color: var(--color-success); background: color-mix(in srgb, var(--color-success) 15%, transparent); } -.cat-error { color: var(--color-danger); background: color-mix(in srgb, var(--color-danger) 15%, transparent); } +.cat-audit { color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent); } +.cat-usage { color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 15%, transparent); } +.cat-error { color: var(--fs-error); background: color-mix(in srgb, var(--fs-error) 15%, transparent); } .method-tag { display: inline-block; font-size: 0.65rem; font-weight: 500; font-family: monospace; padding: 0.05rem 0.25rem; border-radius: 3px; - background: var(--color-bg-secondary); color: var(--color-text-muted); + background: var(--fs-surface-raised); color: var(--fs-text-tertiary); margin-right: 0.25rem; } @@ -2968,7 +2968,7 @@ function formatUserDate(iso: string): string { .version-line { margin: 0; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); display: flex; align-items: center; gap: 0.6rem; @@ -2977,10 +2977,10 @@ function formatUserDate(iso: string): string { font-family: ui-monospace, monospace; font-size: 0.8rem; padding: 0.15rem 0.5rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-primary); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-accent); border-radius: 4px; - color: var(--color-primary); + color: var(--fs-accent); } /* ── Groups tab ──────────────────────────────────────────────── */ @@ -2989,16 +2989,16 @@ function formatUserDate(iso: string): string { .input-field { width: 100%; padding: 0.4rem 0.6rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-surface); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); font-size: 0.875rem; font-family: inherit; outline: none; transition: border-color 0.15s; } -.input-field:focus { border-color: var(--color-primary); } +.input-field:focus { border-color: var(--fs-accent); } .group-create-form { display: flex; @@ -3010,7 +3010,7 @@ function formatUserDate(iso: string): string { .group-create-form .input-field { flex: 1; min-width: 160px; } .loading-msg, .empty-msg { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.88rem; padding: 0.5rem 0; } @@ -3022,8 +3022,8 @@ function formatUserDate(iso: string): string { } .group-card { - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); overflow: hidden; } @@ -3032,7 +3032,7 @@ function formatUserDate(iso: string): string { align-items: center; justify-content: space-between; padding: 0.75rem 1rem; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .group-card-info { @@ -3046,16 +3046,16 @@ function formatUserDate(iso: string): string { .group-name { font-weight: 500; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); } .group-meta { font-size: 0.78rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); white-space: nowrap; } .group-desc { font-size: 0.82rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -3078,8 +3078,8 @@ function formatUserDate(iso: string): string { top: 100%; left: 0; right: 0; - background: var(--color-surface); - border: 1px solid var(--color-border); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); border-radius: 6px; margin-top: 2px; list-style: none; @@ -3098,16 +3098,16 @@ function formatUserDate(iso: string): string { cursor: pointer; transition: background 0.1s; } -.member-result-item:hover { background: var(--color-hover); } +.member-result-item:hover { background: var(--fs-surface-hover); } .member-result-name { font-weight: 500; font-size: 0.85rem; } -.member-result-email { color: var(--color-text-muted); font-size: 0.78rem; } +.member-result-email { color: var(--fs-text-tertiary); font-size: 0.78rem; } .role-select { padding: 0.4rem 0.5rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 6px; - background: var(--color-surface); - color: var(--color-text); + background: var(--fs-surface-hover); + color: var(--fs-text-primary); font-size: 0.85rem; cursor: pointer; font-family: inherit; @@ -3128,10 +3128,10 @@ function formatUserDate(iso: string): string { gap: 0.5rem; padding: 0.4rem 0.5rem; border-radius: 6px; - background: var(--color-hover); + background: var(--fs-surface-hover); } -.member-name { flex: 1; font-size: 0.88rem; font-weight: 500; color: var(--color-text); } +.member-name { flex: 1; font-size: 0.88rem; font-weight: 500; color: var(--fs-text-primary); } .member-role-badge { font-size: 0.7rem; @@ -3141,11 +3141,11 @@ function formatUserDate(iso: string): string { padding: 0.15rem 0.4rem; border-radius: 4px; } -.role-owner { background: color-mix(in srgb, var(--color-warning) 15%, transparent); color: var(--color-warning); } -.role-member { background: color-mix(in srgb, var(--color-muted) 15%, transparent); color: var(--color-muted); } +.role-owner { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning); } +.role-member { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary); } .members-empty { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.82rem; padding: 0.25rem 0.5rem; } @@ -3160,23 +3160,23 @@ function formatUserDate(iso: string): string { font-size: 0.78rem; margin: 0.2rem 0 0; } -.geo-ok { color: var(--color-success); } -.geo-error { color: var(--color-danger); } -.geo-pending { color: var(--color-text-muted); } +.geo-ok { color: var(--fs-success); } +.geo-error { color: var(--fs-error); } +.geo-pending { color: var(--fs-text-tertiary); } .unit-toggle { display: flex; gap: 0; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); overflow: hidden; width: fit-content; margin-top: 0.25rem; } .unit-btn { padding: 0.4rem 1rem; - background: var(--color-bg-card); - color: var(--color-text-muted); + background: var(--fs-surface-raised); + color: var(--fs-text-tertiary); font-size: 0.85rem; cursor: pointer; border: none; @@ -3184,14 +3184,14 @@ function formatUserDate(iso: string): string { transition: background 0.15s, color 0.15s; } .unit-btn:not(:last-child) { - border-right: 1px solid var(--color-border); + border-right: 1px solid var(--fs-border-color); } .unit-btn.active { - background: var(--color-action-primary); + background: var(--fs-action-primary); color: var(--fs-text-on-action); } .unit-btn:hover:not(.active) { - color: var(--color-text); + color: var(--fs-text-primary); } .checkbox-label { @@ -3216,7 +3216,7 @@ function formatUserDate(iso: string): string { } .time-sep { font-size: 1.1rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .time-sep--quiet { font-size: 0.9rem; } @@ -3239,9 +3239,9 @@ function formatUserDate(iso: string): string { cursor: pointer; } .api-key-reveal { - background: color-mix(in srgb, var(--color-primary) 8%, var(--color-surface)); - border: 1px solid color-mix(in srgb, var(--color-primary) 30%, transparent); - border-radius: var(--radius-md); + background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-hover)); + border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent); + border-radius: var(--fs-radius-lg); padding: 1rem; margin-bottom: 1.5rem; } @@ -3253,9 +3253,9 @@ function formatUserDate(iso: string): string { } .api-key-value { flex: 1; - background: var(--color-surface-2); + background: var(--fs-surface-hover); padding: 0.4rem 0.6rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); font-size: 0.85rem; word-break: break-all; } @@ -3267,7 +3267,7 @@ function formatUserDate(iso: string): string { .api-keys-table th, .api-keys-table td { text-align: left; padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.9rem; } .api-keys-table th { font-weight: 500; opacity: 0.7; } @@ -3292,8 +3292,8 @@ function formatUserDate(iso: string): string { align-items: center; gap: 1rem; padding: 0.65rem 0.9rem; - background: color-mix(in srgb, var(--color-primary) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-primary) 25%, transparent); + background: color-mix(in srgb, var(--fs-accent) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent); border-radius: 8px; } .mcp-pkg-name { font-family: monospace; font-size: 0.9rem; flex: 1; } @@ -3303,8 +3303,8 @@ function formatUserDate(iso: string): string { .mcp-code { margin-top: 0.4rem; padding: 0.55rem 0.75rem; - background: color-mix(in srgb, var(--color-text) 6%, transparent); - border: 1px solid var(--color-border); + background: color-mix(in srgb, var(--fs-text-primary) 6%, transparent); + border: 1px solid var(--fs-border-color); border-radius: 6px; font-size: 0.82rem; font-family: monospace; @@ -3317,23 +3317,23 @@ function formatUserDate(iso: string): string { display: flex; gap: 0.25rem; margin-bottom: 1rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .mcp-client-tab { background: transparent; border: none; padding: 0.5rem 0.9rem; font-size: 0.85rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color 0.15s, border-color 0.15s; } -.mcp-client-tab:hover { color: var(--color-text); } +.mcp-client-tab:hover { color: var(--fs-text-primary); } .mcp-client-tab.active { - color: var(--color-primary); - border-bottom-color: var(--color-primary); + color: var(--fs-accent); + border-bottom-color: var(--fs-accent); font-weight: 500; } .mcp-url-block { margin-top: 0.25rem; } @@ -3371,7 +3371,7 @@ function formatUserDate(iso: string): string { .mcp-code-row .btn-sm { white-space: nowrap; } .mcp-advanced { margin-top: 1.25rem; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); padding-top: 0.75rem; } .mcp-advanced summary { @@ -3428,7 +3428,7 @@ function formatUserDate(iso: string): string { list-style: none; margin: 0; padding: 0; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); max-height: 480px; overflow-y: auto; } @@ -3438,19 +3438,19 @@ function formatUserDate(iso: string): string { justify-content: space-between; gap: 1rem; padding: 0.65rem 0.25rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .voice-library-meta { min-width: 0; flex: 1; } .voice-library-id { - font-family: var(--font-mono); + font-family: var(--fs-font-mono); font-size: 0.9rem; font-weight: 500; } .voice-library-sub { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; margin-top: 0.15rem; } @@ -3462,7 +3462,7 @@ function formatUserDate(iso: string): string { } .voice-library-empty { padding: 1rem 0; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); text-align: center; font-style: italic; } @@ -3475,9 +3475,9 @@ function formatUserDate(iso: string): string { letter-spacing: 0.04em; } .voice-badge--bundled { - background: color-mix(in srgb, var(--color-text-muted) 10%, transparent); - color: var(--color-text-muted); - border: 1px solid var(--color-border); + background: color-mix(in srgb, var(--fs-text-tertiary) 10%, transparent); + color: var(--fs-text-tertiary); + border: 1px solid var(--fs-border-color); } .status-badge { font-size: 0.75rem; @@ -3486,14 +3486,14 @@ function formatUserDate(iso: string): string { border-radius: 999px; } .status-on { - background: color-mix(in srgb, var(--color-success) 15%, transparent); - color: var(--color-success); - border: 1px solid color-mix(in srgb, var(--color-success) 40%, transparent); + background: color-mix(in srgb, var(--fs-success) 15%, transparent); + color: var(--fs-success); + border: 1px solid color-mix(in srgb, var(--fs-success) 40%, transparent); } .status-off { - background: color-mix(in srgb, var(--color-text-muted) 10%, transparent); - color: var(--color-text-muted); - border: 1px solid var(--color-border); + background: color-mix(in srgb, var(--fs-text-tertiary) 10%, transparent); + color: var(--fs-text-tertiary); + border: 1px solid var(--fs-border-color); } .radio-group { display: flex; @@ -3513,7 +3513,7 @@ function formatUserDate(iso: string): string { .range-input { width: 100%; max-width: 360px; - accent-color: var(--color-primary); + accent-color: var(--fs-accent); margin: 0.35rem 0 0.2rem; } .range-labels { @@ -3521,7 +3521,7 @@ function formatUserDate(iso: string): string { justify-content: space-between; max-width: 360px; font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .form-actions { margin-top: 1rem; @@ -3539,7 +3539,7 @@ function formatUserDate(iso: string): string { width: 4px; height: 4px; border-radius: 50%; - background: var(--color-text-muted); + background: var(--fs-text-tertiary); animation: va-dot-bounce 1.2s ease-in-out infinite; } .voice-admin-spinner span:nth-child(2) { animation-delay: 0.2s; } @@ -3553,9 +3553,9 @@ function formatUserDate(iso: string): string { margin-bottom: 0.5rem; } .blend-slot { - background: color-mix(in srgb, var(--color-surface) 60%, transparent); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: color-mix(in srgb, var(--fs-surface-hover) 60%, transparent); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 0.75rem 1rem; display: flex; flex-direction: column; @@ -3577,7 +3577,7 @@ function formatUserDate(iso: string): string { font-variant-numeric: tabular-nums; min-width: 2.5rem; text-align: right; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .blend-actions { margin-top: 0.25rem; @@ -3599,37 +3599,37 @@ function formatUserDate(iso: string): string { } .day-btn { padding: 0.3rem 0.65rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 6px; - background: var(--color-bg-card); - color: var(--color-text-muted); + background: var(--fs-surface-raised); + color: var(--fs-text-tertiary); font-size: 0.82rem; cursor: pointer; transition: all 0.15s; font-family: inherit; } -.day-btn:hover { border-color: var(--color-primary); color: var(--color-primary); } +.day-btn:hover { border-color: var(--fs-accent); color: var(--fs-accent); } .day-btn.active { - background: color-mix(in srgb, var(--color-primary) 15%, transparent); - border-color: var(--color-primary); - color: var(--color-primary); + background: color-mix(in srgb, var(--fs-accent) 15%, transparent); + border-color: var(--fs-accent); + color: var(--fs-accent); font-weight: 500; } .learned-summary { - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); - border-left: 3px solid var(--color-primary); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-left: 3px solid var(--fs-accent); border-radius: 8px; padding: 0.85rem 1rem; font-size: 0.9rem; line-height: 1.55; - color: var(--color-text); + color: var(--fs-text-primary); white-space: pre-wrap; margin-bottom: 0.75rem; } .learned-empty { font-size: 0.85rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin-bottom: 0.75rem; } diff --git a/frontend/src/views/SharedWithMeView.vue b/frontend/src/views/SharedWithMeView.vue index 2884543..79e9bf7 100644 --- a/frontend/src/views/SharedWithMeView.vue +++ b/frontend/src/views/SharedWithMeView.vue @@ -106,11 +106,11 @@ onMounted(async () => { font-size: 1.8rem; font-weight: 700; margin: 0; - color: var(--color-text); + color: var(--fs-text-primary); } .loading-state { - color: var(--color-muted); + color: var(--fs-text-tertiary); padding: 2rem; text-align: center; } @@ -124,7 +124,7 @@ onMounted(async () => { font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; - color: var(--color-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.75rem; } @@ -136,9 +136,9 @@ onMounted(async () => { .shared-card { display: flex; - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-xl); overflow: hidden; text-decoration: none; transition: box-shadow 0.15s, transform 0.15s; @@ -151,7 +151,7 @@ onMounted(async () => { .card-color-bar { width: 4px; flex-shrink: 0; - background: var(--color-border); + background: var(--fs-border-color); } .card-body { @@ -163,7 +163,7 @@ onMounted(async () => { .card-title { font-weight: 600; font-size: 0.95rem; - color: var(--color-text); + color: var(--fs-text-primary); margin-bottom: 0.3rem; white-space: nowrap; overflow: hidden; @@ -179,12 +179,12 @@ onMounted(async () => { .card-owner { font-size: 0.78rem; - color: var(--color-muted); + color: var(--fs-text-tertiary); } .card-desc { font-size: 0.82rem; - color: var(--color-muted); + color: var(--fs-text-tertiary); margin: 0; overflow: hidden; display: -webkit-box; @@ -203,25 +203,25 @@ onMounted(async () => { align-items: center; gap: 0.6rem; padding: 0.6rem 0.9rem; - background: var(--color-surface); - border: 1px solid var(--color-border); + background: var(--fs-surface-hover); + border: 1px solid var(--fs-border-color); border-radius: 8px; text-decoration: none; transition: background 0.1s; } .shared-row:hover { - background: var(--color-hover); + background: var(--fs-surface-hover); } .row-icon { font-size: 0.9rem; flex-shrink: 0; - color: var(--color-muted); + color: var(--fs-text-tertiary); } .row-title { flex: 1; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); font-weight: 500; white-space: nowrap; overflow: hidden; @@ -229,7 +229,7 @@ onMounted(async () => { } .row-owner { font-size: 0.78rem; - color: var(--color-muted); + color: var(--fs-text-tertiary); white-space: nowrap; } @@ -242,12 +242,12 @@ onMounted(async () => { 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); } +.perm-viewer { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary); } +.perm-editor { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent); } +.perm-admin { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning); } .empty-msg { - color: var(--color-muted); + color: var(--fs-text-tertiary); font-size: 0.88rem; margin: 0; padding: 1rem 0; diff --git a/frontend/src/views/SnippetDetailView.vue b/frontend/src/views/SnippetDetailView.vue index 625d1c2..d2a0360 100644 --- a/frontend/src/views/SnippetDetailView.vue +++ b/frontend/src/views/SnippetDetailView.vue @@ -205,7 +205,7 @@ async function confirmDelete() { .snippet-detail { max-width: 820px; margin: 2rem auto; - padding: 0 var(--page-padding-x); + padding: 0 var(--fs-layout-page-pad); overflow-x: clip; } @@ -213,20 +213,20 @@ async function confirmDelete() { display: inline-block; margin-bottom: 1rem; font-size: 0.85rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); text-decoration: none; } .back-link:hover { - color: var(--color-primary); + color: var(--fs-accent); } .state-msg { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.9rem; margin-top: 1rem; } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; margin-top: 1rem; } @@ -239,7 +239,7 @@ async function confirmDelete() { } .snippet-name { margin: 0; - font-family: var(--font-mono); + font-family: var(--fs-font-mono); font-size: 1.4rem; word-break: break-word; } @@ -253,7 +253,7 @@ async function confirmDelete() { .when-to-use { margin: 0.75rem 0 1.25rem; font-size: 1rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); line-height: 1.55; } @@ -262,12 +262,12 @@ async function confirmDelete() { .shared-notice { margin: 0.75rem 0 0; padding: 0.6rem 0.85rem; - border-left: 3px solid var(--color-text-muted); + border-left: 3px solid var(--fs-text-tertiary); border-radius: 6px; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); font-size: 0.85rem; line-height: 1.5; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .meta-grid { @@ -280,23 +280,23 @@ async function confirmDelete() { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding-top: 0.15rem; } .meta-grid dd { margin: 0; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); min-width: 0; } .meta-grid code, .tag-row + * code { - font-family: var(--font-mono); + font-family: var(--fs-font-mono); font-size: 0.82rem; - background: color-mix(in srgb, var(--color-primary) 12%, transparent); - color: var(--color-primary); + background: color-mix(in srgb, var(--fs-accent) 12%, transparent); + color: var(--fs-accent); padding: 0.08rem 0.35rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); word-break: break-all; } .location-list { @@ -317,7 +317,7 @@ async function confirmDelete() { align-items: baseline; } .merged-hint { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.8rem; } .merged-entry { @@ -328,15 +328,15 @@ async function confirmDelete() { .unmerge-btn { font-size: 0.72rem; padding: 0.05rem 0.35rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); border-radius: 4px; background: transparent; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); cursor: pointer; } .unmerge-btn:hover:not(:disabled) { - color: var(--color-text); - border-color: var(--color-text-muted); + color: var(--fs-text-primary); + border-color: var(--fs-text-tertiary); } .unmerge-btn:disabled { opacity: 0.6; @@ -344,30 +344,30 @@ async function confirmDelete() { } .unmerge-na { font-size: 0.72rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); /* Cursor cues that the explanation is in the tooltip. */ cursor: help; } .code-block { - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); overflow: hidden; - background: var(--color-bg); + background: var(--fs-surface-page); } .code-bar { display: flex; align-items: center; justify-content: space-between; padding: 0.4rem 0.5rem 0.4rem 0.85rem; - border-bottom: 1px solid var(--color-border); - background: var(--color-bg-secondary); + border-bottom: 1px solid var(--fs-border-color); + background: var(--fs-surface-raised); } .code-lang { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .btn-copy { padding: 0.2rem 0.65rem; @@ -379,10 +379,10 @@ async function confirmDelete() { overflow-x: auto; } .code-block code { - font-family: var(--font-mono); + font-family: var(--fs-font-mono); font-size: 0.85rem; line-height: 1.6; - color: var(--color-text); + color: var(--fs-text-primary); white-space: pre; } @@ -396,9 +396,9 @@ async function confirmDelete() { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: 999px; - background: var(--color-bg-secondary); - color: var(--color-text-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + color: var(--fs-text-secondary); + border: 1px solid var(--fs-border-color); } @media (max-width: 600px) { diff --git a/frontend/src/views/SnippetEditorView.vue b/frontend/src/views/SnippetEditorView.vue index 435a0ba..30a4f7a 100644 --- a/frontend/src/views/SnippetEditorView.vue +++ b/frontend/src/views/SnippetEditorView.vue @@ -365,7 +365,7 @@ function cancel() { .snippet-editor { max-width: 760px; margin: 2rem auto; - padding: 0 var(--page-padding-x); + padding: 0 var(--fs-layout-page-pad); overflow-x: clip; } .snippet-editor h1 { @@ -376,19 +376,19 @@ function cancel() { display: inline-block; margin-bottom: 1rem; font-size: 0.85rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); text-decoration: none; } .back-link:hover { - color: var(--color-primary); + color: var(--fs-accent); } .state-msg { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.9rem; } .error-msg { - color: var(--color-danger); + color: var(--fs-error); font-size: 0.9rem; } @@ -415,27 +415,27 @@ function cancel() { .location-set legend { font-size: 0.8rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); } .required { - color: var(--color-danger); + color: var(--fs-error); } .hint { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0.1rem 0 0; } .hint-inline { font-weight: 400; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .input { padding: 0.5rem 0.7rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - background: var(--color-bg); - color: var(--color-text); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); + background: var(--fs-surface-page); + color: var(--fs-text-primary); font-size: 0.9rem; font-family: inherit; box-sizing: border-box; @@ -443,11 +443,11 @@ function cancel() { } .input:focus { outline: none; - border-color: var(--color-primary); - box-shadow: var(--focus-ring); + border-color: var(--fs-accent); + box-shadow: var(--fs-focus-ring); } .mono { - font-family: var(--font-mono); + font-family: var(--fs-font-mono); } .code-area { resize: vertical; @@ -459,8 +459,8 @@ function cancel() { } .location-set { - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 0.85rem 1rem 1rem; margin: 0; } @@ -477,32 +477,32 @@ function cancel() { .loc-remove { width: 2rem; height: 2rem; - border: 1px solid var(--color-border); + border: 1px solid var(--fs-border-color); background: transparent; - color: var(--color-text-muted); - border-radius: var(--radius-sm); + color: var(--fs-text-tertiary); + border-radius: var(--fs-radius-sm); cursor: pointer; font-size: 1.1rem; line-height: 1; } .loc-remove:hover { - border-color: var(--color-danger); - color: var(--color-danger); + border-color: var(--fs-error); + color: var(--fs-error); } .loc-add { margin-top: 0.15rem; padding: 0.35rem 0.7rem; - border: 1px dashed var(--color-border); + border: 1px dashed var(--fs-border-color); background: transparent; - color: var(--color-text-secondary); - border-radius: var(--radius-sm); + color: var(--fs-text-secondary); + border-radius: var(--fs-radius-sm); cursor: pointer; font-size: 0.82rem; font-family: inherit; } .loc-add:hover { - border-color: var(--color-primary); - color: var(--color-primary); + border-color: var(--fs-accent); + color: var(--fs-accent); } @media (max-width: 600px) { @@ -514,7 +514,7 @@ function cancel() { .field-label { font-size: 0.8rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); } .systems { display: flex; @@ -528,11 +528,11 @@ function cancel() { align-items: center; gap: 0.45rem; font-size: 0.85rem; - color: var(--color-text); + color: var(--fs-text-primary); cursor: pointer; } .system-opt input { - accent-color: var(--color-primary); + accent-color: var(--fs-accent); cursor: pointer; } @@ -541,25 +541,25 @@ function cancel() { flex-direction: column; gap: 0.4rem; padding: 0.85rem 1rem; - border: 1px solid var(--color-border); - border-left: 3px solid var(--color-warning); + border: 1px solid var(--fs-border-color); + border-left: 3px solid var(--fs-warning); border-radius: 8px; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .duplicate-title { margin: 0; font-size: 0.85rem; font-weight: 500; - color: var(--color-text); + color: var(--fs-text-primary); } .duplicate-body { margin: 0; font-size: 0.8rem; line-height: 1.5; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .duplicate-body a { - color: var(--color-primary); + color: var(--fs-accent); } .duplicate-actions { display: flex; diff --git a/frontend/src/views/SnippetListView.vue b/frontend/src/views/SnippetListView.vue index ed622d3..f9cb400 100644 --- a/frontend/src/views/SnippetListView.vue +++ b/frontend/src/views/SnippetListView.vue @@ -519,9 +519,9 @@ function usageTitle(s: SnippetListItem): string { \ No newline at end of file diff --git a/frontend/src/views/TaskViewerView.vue b/frontend/src/views/TaskViewerView.vue index dc98184..cbdc077 100644 --- a/frontend/src/views/TaskViewerView.vue +++ b/frontend/src/views/TaskViewerView.vue @@ -475,7 +475,7 @@ const subTaskProgress = computed(() => { gap: 0.5rem; flex-wrap: wrap; font-size: 0.83rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.75rem; } .meta-item { @@ -494,10 +494,10 @@ const subTaskProgress = computed(() => { } .due-date { font-size: 0.85rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .due-date.overdue { - color: var(--color-overdue); + color: var(--fs-overdue); font-weight: 500; } .task-meta-row { @@ -508,10 +508,10 @@ const subTaskProgress = computed(() => { } .task-meta-item { font-size: 0.78rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .task-meta-recurrence { - color: var(--color-primary); + color: var(--fs-accent); font-weight: 500; } .tags { @@ -524,7 +524,7 @@ const subTaskProgress = computed(() => { /* Sub-tasks */ .subtasks { margin-top: 2rem; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); padding-top: 1rem; } .subtasks-header { @@ -540,21 +540,21 @@ const subTaskProgress = computed(() => { } .subtasks-progress { font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .subtasks-pct { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .subtasks-track { height: 4px; - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); border-radius: 2px; margin-bottom: 0.75rem; overflow: hidden; } .subtasks-fill { height: 100%; - background: var(--color-status-done); + background: var(--fs-status-done); border-radius: 2px; transition: width 0.3s ease; } @@ -571,10 +571,10 @@ const subTaskProgress = computed(() => { align-items: center; gap: 0.5rem; padding: 0.3rem 0.5rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); } .subtask-row:hover { - background: var(--color-bg-secondary); + background: var(--fs-surface-raised); } .sub-dot { flex-shrink: 0; @@ -592,21 +592,21 @@ const subTaskProgress = computed(() => { } .dot-todo { background: transparent; - border: 2px solid var(--color-text-muted); + border: 2px solid var(--fs-text-tertiary); } .dot-in-progress { - background: var(--color-status-in-progress); + background: var(--fs-status-in-progress); } .dot-done { - background: var(--color-status-done); + background: var(--fs-status-done); } .dot-cancelled { - background: var(--color-text-muted); + background: var(--fs-text-tertiary); } .sub-title { flex: 1; font-size: 0.9rem; - color: var(--color-text); + color: var(--fs-text-primary); text-decoration: none; min-width: 0; overflow: hidden; @@ -614,21 +614,21 @@ const subTaskProgress = computed(() => { white-space: nowrap; } .sub-title:hover { - color: var(--color-primary); + color: var(--fs-accent); } .sub-title.sub-done { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); text-decoration: line-through; } .sub-due { font-size: 0.75rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); flex-shrink: 0; } .backlinks { margin-top: 2.5rem; - border-top: 1px solid var(--color-border); + border-top: 1px solid var(--fs-border-color); padding-top: 1.25rem; } .backlinks-heading { @@ -639,14 +639,14 @@ const subTaskProgress = computed(() => { font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.75rem; } .backlinks-count { margin-left: 0.2rem; font-size: 0.72rem; - background: var(--color-bg-secondary); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); border-radius: 999px; padding: 0 0.4rem; line-height: 1.4; @@ -661,18 +661,18 @@ const subTaskProgress = computed(() => { align-items: center; gap: 0.6rem; padding: 0.5rem 0.75rem; - border-radius: var(--radius-md); - background: var(--color-bg-card); - border: 1px solid var(--color-border); + border-radius: var(--fs-radius-lg); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); text-decoration: none; - color: var(--color-text); + color: var(--fs-text-primary); transition: border-color 0.15s, box-shadow 0.15s; font-size: 0.9rem; } .backlink-card:hover { - border-color: color-mix(in srgb, var(--color-primary) 50%, transparent); + border-color: color-mix(in srgb, var(--fs-accent) 50%, transparent); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - color: var(--color-primary); + color: var(--fs-accent); } .backlink-type-badge { font-size: 0.68rem; @@ -684,9 +684,9 @@ const subTaskProgress = computed(() => { flex-shrink: 0; } .badge-note { - background: color-mix(in srgb, var(--color-primary) 12%, transparent); - color: var(--color-primary); - border: 1px solid color-mix(in srgb, var(--color-primary) 25%, transparent); + background: color-mix(in srgb, var(--fs-accent) 12%, transparent); + color: var(--fs-accent); + border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent); } .badge-task { background: color-mix(in srgb, #f59e0b 12%, transparent); @@ -716,12 +716,12 @@ const subTaskProgress = computed(() => { .skel-meta, .skel-badges, .skel-line { - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); background: linear-gradient( 90deg, - var(--color-bg-secondary) 25%, - color-mix(in srgb, var(--color-text-muted) 18%, var(--color-bg-secondary)) 50%, - var(--color-bg-secondary) 75% + var(--fs-surface-raised) 25%, + color-mix(in srgb, var(--fs-text-tertiary) 18%, var(--fs-surface-raised)) 50%, + var(--fs-surface-raised) 75% ); background-size: 200% 100%; animation: skel-shine 1.5s ease infinite; @@ -733,7 +733,7 @@ const subTaskProgress = computed(() => { } .skel-btn { width: 70px; height: 32px; } .skel-btn--wide { width: 90px; } -.skel-title { height: 2.2rem; width: 65%; border-radius: var(--radius-md); } +.skel-title { height: 2.2rem; width: 65%; border-radius: var(--fs-radius-lg); } .skel-meta { height: 0.85rem; width: 45%; } .skel-badges { height: 1.6rem; width: 30%; border-radius: 999px; } .skel-line { height: 0.9rem; } @@ -742,26 +742,26 @@ const subTaskProgress = computed(() => { /* ── Goal block + auto-summary banner ─────────────────────────────────────── */ .task-goal-display { - border-left: 2px solid var(--color-border); + border-left: 2px solid var(--fs-border-color); padding: 0.4rem 0 0.4rem 0.9rem; margin: 0.75rem 0 1.25rem; background: rgba(255, 255, 255, 0.02); } .goal-label { - font-family: var(--font-display); + font-family: var(--fs-font-display); font-style: italic; font-size: 0.78rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); margin: 0 0 0.25rem; } .goal-text { margin: 0; font-size: 0.95rem; line-height: 1.45; - color: var(--color-text); + color: var(--fs-text-primary); white-space: pre-wrap; } diff --git a/frontend/src/views/TrashView.vue b/frontend/src/views/TrashView.vue index e954f60..0301312 100644 --- a/frontend/src/views/TrashView.vue +++ b/frontend/src/views/TrashView.vue @@ -68,7 +68,7 @@ onMounted(() => store.fetchTrash()); .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); } +.batch-actions button { border-radius: 6px; padding: 0.35rem 0.7rem; cursor: pointer; border: 1px solid var(--fs-border-color); background: none; color: inherit; } +.btn-restore:hover { border-color: var(--fs-action-primary); color: var(--fs-action-primary); } +.btn-purge:hover { border-color: var(--fs-action-destructive); color: var(--fs-action-destructive); } diff --git a/frontend/src/views/UserManagementView.vue b/frontend/src/views/UserManagementView.vue index ce0918f..f933ac3 100644 --- a/frontend/src/views/UserManagementView.vue +++ b/frontend/src/views/UserManagementView.vue @@ -297,9 +297,9 @@ function formatDate(iso: string): string { margin: 0 0 1.5rem; } .settings-section { - background: var(--color-bg-card); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); + background: var(--fs-surface-raised); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; } @@ -317,16 +317,16 @@ function formatDate(iso: string): string { .invite-input { flex: 1; padding: 0.5rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); + border: 1px solid var(--fs-border-color); + border-radius: var(--fs-radius-sm); font-size: 0.95rem; - background: var(--color-bg); - color: var(--color-text); + background: var(--fs-surface-page); + color: var(--fs-text-primary); box-sizing: border-box; } .invite-input:focus { outline: none; - border-color: var(--color-primary); + border-color: var(--fs-accent); } .invite-list { margin-top: 1rem; @@ -334,7 +334,7 @@ function formatDate(iso: string): string { .invite-list h3 { margin: 0 0 0.5rem; font-size: 0.95rem; - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } /* Registration toggle */ @@ -352,33 +352,33 @@ function formatDate(iso: string): string { font-size: 0.95rem; } .text-success { - color: var(--color-success); + color: var(--fs-success); } .text-muted { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } .field-hint { margin: 0.35rem 0 0; font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } /* The one genuine override: 'close registration' must NOT read as the primary action it sits on. Scoped, so it beats the shared variant. */ .btn-toggle-close { - background: var(--color-bg-secondary); - color: var(--color-text); - border: 1px solid var(--color-border); + background: var(--fs-surface-raised); + color: var(--fs-text-primary); + border: 1px solid var(--fs-border-color); } .btn-toggle-close:hover:not(:disabled) { - border-color: var(--color-warning); - color: var(--color-warning); + border-color: var(--fs-warning); + color: var(--fs-warning); } /* Users table */ .loading-msg, .empty-msg { text-align: center; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.9rem; padding: 1rem 0; } @@ -392,13 +392,13 @@ function formatDate(iso: string): string { font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); padding: 0.5rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); } .users-table td { padding: 0.65rem 0.75rem; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--fs-border-color); font-size: 0.9rem; } .users-table tbody tr:last-child td { @@ -408,10 +408,10 @@ function formatDate(iso: string): string { font-weight: 600; } .cell-email { - color: var(--color-text-secondary); + color: var(--fs-text-secondary); } .cell-date { - color: var(--color-text-muted); + color: var(--fs-text-tertiary); font-size: 0.85rem; } .cell-actions { @@ -426,21 +426,21 @@ function formatDate(iso: string): string { text-transform: uppercase; letter-spacing: 0.05em; padding: 0.15rem 0.4rem; - border-radius: var(--radius-sm); + border-radius: var(--fs-radius-sm); } .role-admin { - color: var(--color-primary); - background: color-mix(in srgb, var(--color-primary) 15%, transparent); + color: var(--fs-accent); + background: color-mix(in srgb, var(--fs-accent) 15%, transparent); } .role-user { - color: var(--color-text-muted); - background: var(--color-bg-secondary); + color: var(--fs-text-tertiary); + background: var(--fs-surface-raised); } /* Action buttons */ .you-label { font-size: 0.8rem; - color: var(--color-text-muted); + color: var(--fs-text-tertiary); } @media (max-width: 768px) {