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) { 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/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/routes/search.py b/src/scribe/routes/search.py index bdfe6b5..2cc891c 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") @@ -34,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/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..1d5517e 100644 --- a/src/scribe/services/knowledge.py +++ b/src/scribe/services/knowledge.py @@ -396,17 +396,37 @@ 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) + import time as _time + _t0 = _time.perf_counter() 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, ) + # 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/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/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/src/scribe/services/plugin_context.py b/src/scribe/services/plugin_context.py index 39a8537..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)} @@ -326,6 +339,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 +349,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 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() 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" + ) 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