Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions web/scripts/style-guard.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const TOKENS = [
];
const TEXT_UTILITIES =
"left|center|right|justify|start|end|ellipsis|clip|wrap|nowrap|balance|pretty|white|black|transparent|current|inherit";
/* `white` / `black` 留在这张表里,为的是让 `unknown-text` 放过它们——
它们归下面 `raw-white` 一条管,两条都报会把同一个错说两遍 */
const KNOWN_TEXT = [...TOKENS, ...TEXT_UTILITIES.split("|")]
.sort((a, b) => b.length - a.length)
.join("|");
Expand Down Expand Up @@ -63,9 +65,16 @@ const RULES = [
ui: true,
},
{
id: "alpha-white",
re: /\b(bg|border|text|ring|outline|divide)-(white|black)\/\[?[0-9.]+\]?/g,
why: "白色透明度是 surface / surface-2 / surface-3 / line 的事,不在页面里调(规矩 4)",
id: "raw-white",
/* 白与黑都不是颜色令牌,**带不带透明度都一样**。从前这条只认 `text-white/10`
那种带斜杠的写法,`text-white` 光秃秃地写就过了——`white` 还被算进下面
`unknown-text` 的合法工具类里(它跟 text-left、text-nowrap 排在一起)。
两道门都开着,于是顶栏的字标写死了白色:暗底上看不出来,翻成纸底之后
它还是白的,等于隐形。名字要的是 `text-ink`,随主题走。
真要"在深色块上永远是浅字"(主按钮、危险按钮里的字)另有令牌:
`--u-on-accent` / `--u-on-danger`,它们在浅色那套里也是对的 */
re: /\b(bg|border|text|ring|outline|divide|fill|stroke|placeholder|decoration|from|via|to)-(white|black)\b(\/\[?[0-9.]+\]?)?/g,
why: "白与黑不是令牌:文字用 ink / ink-2,面用 surface / surface-2 / surface-3,线用 line;深色块上的字用 on-accent / on-danger(规矩 4)",
ui: true,
},
{
Expand Down Expand Up @@ -116,6 +125,16 @@ const RULES = [
skip: (rel) => /src\/(pages\/graphVisuals\.ts|palette\.ts)$/.test(rel) || /\.test\.tsx?$/.test(rel),
allow: /rgba\(0,\s*0,\s*0,\s*0\)/g,
},
{
id: "raw-shadow",
/* Tailwind 自带的阴影是按浅色界面调的黑影,与 `--u-shadow` 不是一回事;
暗底上它们几乎看不见,所以能一直混在页面里没人察觉。浮起只有两档:
贴着画布的控件 `u-lift`,盖在页面上的浮层 `u-lift-strong`。
`shadow-none` 是"把它取消掉",不是一档深浅,放行 */
re: /\bshadow-(sm|md|lg|xl|2xl)\b/g,
why: "浮起两档:u-lift(贴着画布的控件)/ u-lift-strong(浮层);深浅归 --u-shadow(规矩 4)",
ui: true,
},
{
id: "native-confirm",
re: /\bwindow\.(confirm|alert)\(|(?<![.\w])(confirm|alert)\(/g,
Expand Down
3 changes: 1 addition & 2 deletions web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,7 @@ export const en = {
notion: "Notion",
},
sourceKindHints: {
folder:
"A plain folder. Select it and upload (or drag) files straight into it — nothing is watched or synced.",
folder: "Holds the files you upload: add, drag in and remove them here.",
url: "Fetches the listed web pages; changed pages update the same document.",
rss: "Subscribes to a feed; each entry becomes a document dated by its publish time.",
jira_issues:
Expand Down
5 changes: 2 additions & 3 deletions web/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,7 @@ export const zh: Strings = {
notion: "Notion",
},
sourceKindHints: {
folder:
"一个普通文件夹。选中它,把文件直接上传(或拖)进去——不监听、不同步。",
folder: "可在文件夹中管理上传的文件:上传、拖入、删除都在这里。",
url: "抓取所列的网页;内容变化时更新同一篇文档。",
rss: "订阅一个源;每条目成为一篇文档,日期取其发布时间。",
jira_issues:
Expand Down Expand Up @@ -642,7 +641,7 @@ export const zh: Strings = {
chunkOf: (filename: string, seq: number) => `${filename} · 第 ${seq} 段`,
},
ask: {
greeting: "问问 Utopia 记得什么",
greeting: "问问 Utopia 都记住了什么",
emptyTitle: "对话",
emptyBody:
"与你的知识库对话——带引用的回答、关于时间的提问,而且它会记住。\n请先在「文库」上传文档,并在「管理 → 模型」里配置模型。",
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/AlertBell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ function Panel({ panelRef, onClose }: { panelRef: Ref<HTMLDivElement>; onClose:
// top-0 而不是 top-9:面板要从铃铛**原位**长出来,右上角对齐
<div
ref={panelRef}
className="u-menu-glass absolute right-0 top-0 w-[420px] rounded-overlay shadow-2xl z-50 overflow-hidden"
className="u-menu-glass absolute right-0 top-0 w-[420px] rounded-overlay u-lift-strong z-50 overflow-hidden"
>
{/* 第一行就是关掉这张面板——同库切换器:面板从铃铛原位长出来,
右端那个朝上的三角正落在铃铛上,"再点一下缩回去"。
Expand Down
14 changes: 10 additions & 4 deletions web/src/pages/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ export function Chat() {
/>
</Button>
{scopeOpen && (
<div className="u-menu-glass u-pop-up absolute bottom-full mb-2 left-0 z-50 w-56 rounded-overlay shadow-2xl overflow-hidden">
<div className="u-menu-glass u-pop-up absolute bottom-full mb-2 left-0 z-50 w-56 rounded-overlay u-lift-strong overflow-hidden">
<div className="border-b border-line px-4 py-3 text-body font-medium text-ink">
{S.ask.scopeLabel}
</div>
Expand All @@ -424,13 +424,19 @@ export function Chat() {
key={k.id}
density="menu"
active={k.id === kb?.id}
/* 对勾走 `trailing` 槽。**塞进 children 的 svg 会掉到第二行**:
Row 把 children 整个包进一个 `flex-1 truncate` 的 span,
那个 span 不是 flex 容器,preflight 又把 svg 设成块级,
于是勾自己占一行、整行跟着变高(顶栏的库切换器早就这么写的) */
trailing={
k.id === kb?.id ? <Check size={12} className="text-ink-2" /> : undefined
}
onClick={() => {
setScopeOpen(false);
if (k.id !== kb?.id) setKb(k.id);
}}
>
<span className="flex-1 min-w-0 truncate">{k.name}</span>
{k.id === kb?.id && <Check size={12} className="shrink-0 text-ink-2" />}
<span className="truncate">{k.name}</span>
</Row>
))}
</div>
Expand Down Expand Up @@ -569,7 +575,7 @@ export function Chat() {
className="fixed inset-0 z-10"
onClick={() => setMenuFor(null)}
/>
<div className="glass-strong absolute right-2 top-8 z-20 w-32 rounded-overlay py-1 shadow-xl">
<div className="glass-strong absolute right-2 top-8 z-20 w-32 rounded-overlay py-1 u-lift-strong">
<Row
density="menu"
onClick={() => {
Expand Down
7 changes: 4 additions & 3 deletions web/src/pages/Docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export function DocsPage() {
)}
</div>
{q.trim().length >= 2 && (
<div className="u-menu-glass u-pop-in u-pop-in-tl absolute inset-x-0 top-full mt-2 rounded-overlay shadow-2xl overflow-hidden">
<div className="u-menu-glass u-pop-in u-pop-in-tl absolute inset-x-0 top-full mt-2 rounded-overlay u-lift-strong overflow-hidden">
{results.length === 0 ? (
<p className="px-4 py-3 text-small text-ink-2">{S.docs.noResults}</p>
) : (
Expand Down Expand Up @@ -267,8 +267,9 @@ export function DocsPage() {
{/* 上下都留足空白:标题不顶着头,末段内容能滚到屏幕中部——人读屏幕中间。
排版交给官方 @tailwindcss/typography(prose,16px 基准),
自定义只剩:标题锚点 id、外链新开、表格横向滚动容器 */}
{/* prose-neutral:默认 gray 阶带蓝相(oklch 258°),违反 chrome 零色偏 */}
<article className="prose prose-neutral prose-invert prose-headings:scroll-mt-6 prose-code:before:content-none prose-code:after:content-none flex-1 min-w-0 max-w-3xl mx-auto px-8 pt-16 pb-[40vh]">
{/* 颜色走 `.u-doc-prose`(styles.css):插件的 neutral 灰阶带蓝相、
invert 变体是写死的暗色,两个都不用,接令牌 */}
<article className="prose u-doc-prose prose-headings:scroll-mt-6 prose-code:before:content-none prose-code:after:content-none flex-1 min-w-0 max-w-3xl mx-auto px-8 pt-16 pb-[40vh]">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
Expand Down
22 changes: 13 additions & 9 deletions web/src/pages/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1257,7 +1257,7 @@ export function Graph() {
{/* 与本体页左栏的过滤框同一副身材、同一个角落(见 Ontology.tsx) */}
<Input
icon={<Search size={12} />}
className="w-58 shadow-lg"
className="w-58 u-lift"
placeholder={
inSubgraph ? S.graph.searchInSubgraph : S.graph.searchEntity
}
Expand All @@ -1268,7 +1268,7 @@ export function Graph() {
}}
/>
{searchQ && searchHits.length > 0 && (
<div className="glass-strong absolute mt-1 w-full rounded-overlay shadow-xl overflow-hidden">
<div className="glass-strong absolute mt-1 w-full rounded-overlay u-lift-strong overflow-hidden">
{searchHits.map((c) => (
<Row
key={c.id}
Expand All @@ -1281,14 +1281,18 @@ export function Graph() {
setSearchQ("");
}}
>
<span className="flex items-center gap-2">
<span className="flex min-w-0 items-center gap-2">
<span
className="h-2.5 w-2.5 shrink-0 rounded-full"
style={{ background: c.color }}
/>
{/* **名字最后才让**:两段都是 truncate 的话,flex 按各自
的宽度一起收,一个长一点的消歧词能把名字挤成「Ac…」。
消歧词收得快四倍、且最多占四成——它是补充,名字才是
这一行要读的东西 */}
<span className="truncate">{c.name}</span>
{c.disambiguator && (
<span className="truncate text-small text-ink-2">
{c.disambiguator && c.disambiguator !== c.type_label && (
<span className="min-w-0 max-w-[40%] shrink-[4] truncate text-small text-ink-2">
· {c.disambiguator}
</span>
)}
Expand Down Expand Up @@ -1317,7 +1321,7 @@ export function Graph() {
{focusEntity && (
<Button
variant="secondary"
className="glass-strong pointer-events-auto shadow-lg"
className="glass-strong pointer-events-auto u-lift"
onClick={() => setFocusEntity(null)}
>
{S.graph.backToOverview}
Expand Down Expand Up @@ -1374,7 +1378,7 @@ export function Graph() {
{legendPop.open && (
<div
ref={legendPop.panelRef}
className="u-menu-glass absolute left-0 top-0 z-50 w-72 overflow-hidden rounded-overlay shadow-2xl"
className="u-menu-glass absolute left-0 top-0 z-50 w-72 overflow-hidden rounded-overlay u-lift-strong"
>
{/* 与库切换器、告警面板、用户菜单同一副解剖:第一行是触发它的
那个胶囊自己,三角翻上去,点它缩回;没有浮在角上的关闭叉
Expand Down Expand Up @@ -2216,7 +2220,7 @@ function DerivedPanel({
return (
<div
ref={panelRef}
className="u-menu-glass pointer-events-auto absolute bottom-0 left-0 z-50 w-72 overflow-hidden rounded-overlay px-3 pb-3 pt-3 shadow-2xl"
className="u-menu-glass pointer-events-auto absolute bottom-0 left-0 z-50 w-72 overflow-hidden rounded-overlay px-3 pb-3 pt-3 u-lift-strong"
>
{/* items-center 而不是 baseline:标题旁边站着一个按钮和一个关闭键,
按基线对齐会让那两个看着往上飘 */}
Expand Down Expand Up @@ -2740,7 +2744,7 @@ function EntityPanel({

return (
<div
className={`${exiting ? "u-dock-out" : "u-dock-in"} glass-strong absolute top-14 right-3 bottom-20 w-96 z-10 rounded-overlay shadow-2xl flex flex-col`}
className={`${exiting ? "u-dock-out" : "u-dock-in"} glass-strong absolute top-14 right-3 bottom-20 w-96 z-10 rounded-overlay u-lift-strong flex flex-col`}
>
<div className="flex items-start justify-between px-4 py-4 border-b border-line">
<div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/KbSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function KbSwitcher({
<div
ref={panelRef}
// -left-2:胶囊只有 8 的内距,面板行有 16——面板左移 8,行里的图标才与胶囊的图标同一个 x
className="u-menu-glass absolute -left-2 top-0 z-50 w-max min-w-64 max-w-80 overflow-hidden rounded-overlay shadow-2xl"
className="u-menu-glass absolute -left-2 top-0 z-50 w-max min-w-64 max-w-80 overflow-hidden rounded-overlay u-lift-strong"
>
{/* 第一行是胶囊自己:同一个图标、同一个名字,箭头翻上去;再点一下缩回 */}
<div
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/Ontology.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ function DockedPanel({
<div
// 与图谱页的实体面板同一副壳:同宽(w-96)、同一个顶部起点(给顶上那排
// 药丸让位),同一个头部解剖。两页并排看是同一件东西
className={`${exiting ? "u-dock-out" : "u-dock-in"} glass-strong absolute top-3 right-3 bottom-3 w-96 z-10 rounded-overlay shadow-2xl flex flex-col`}
className={`${exiting ? "u-dock-out" : "u-dock-in"} glass-strong absolute top-3 right-3 bottom-3 w-96 z-10 rounded-overlay u-lift-strong flex flex-col`}
>
<div className="shrink-0 flex items-start justify-between gap-2 px-4 py-4 border-b border-line">
<div className="min-w-0">{header}</div>
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/OntologySchemaGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ export function OntologySchemaGraph({
{unscopedPop.open && (
<div
ref={unscopedPop.panelRef}
className="u-menu-glass absolute left-0 top-0 z-50 w-72 overflow-hidden rounded-overlay shadow-2xl"
className="u-menu-glass absolute left-0 top-0 z-50 w-72 overflow-hidden rounded-overlay u-lift-strong"
>
{/* 与库切换器、告警面板、图谱页的类图例同一副解剖:第一行就是
触发它的那个胶囊自己,三角翻上去,点它收回;**没有浮在角上的
Expand Down
22 changes: 13 additions & 9 deletions web/src/pages/ReviewOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,21 @@ function DailyBars({ days }: { days: ReviewSummary["decided"]["daily"] }) {
const max = Math.max(1, ...days.map((d) => d.count));
return (
<div>
{/* 一天一格、格里居中一根柱,**柱子本身有上限宽**。从前是 `flex-1`
直接铺满:这张卡横着能有一千多像素,十四根柱子就成了十四块板,
有数的那天看着像一堵墙而不是一根柱 */}
<div className="flex h-16 items-end gap-1">
{days.map((d) => (
<div
key={d.day}
className="flex-1 rounded-none bg-accent"
style={{
height: `${Math.max(2, Math.round((d.count / max) * 64))}px`,
opacity: d.count === 0 ? 0.25 : 1,
}}
title={`${d.day} · ${d.count}`}
/>
<div key={d.day} className="flex h-full flex-1 items-end justify-center">
<div
className="w-full max-w-[18px] rounded-none bg-bar"
style={{
height: `${Math.max(2, Math.round((d.count / max) * 64))}px`,
opacity: d.count === 0 ? 0.25 : 1,
}}
title={`${d.day} · ${d.count}`}
/>
</div>
))}
</div>
<div className="mt-1 flex justify-between text-fine text-ink-2">
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function UserMenu({ user }: { user: User }) {
{open && (
<div
ref={panelRef}
className="u-menu-glass absolute right-0 top-0 w-64 rounded-overlay shadow-2xl z-50 overflow-hidden"
className="u-menu-glass absolute right-0 top-0 w-64 rounded-overlay u-lift-strong z-50 overflow-hidden"
>
{/* 身份头:再点一下缩回胶囊 */}
<div
Expand Down
22 changes: 22 additions & 0 deletions web/src/pages/graphVisuals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { mix } from "./graphVisuals";

/* 混色的两端都可能是令牌读回来的 `rgb(...)`,不只是 `#rrggbb`。
认不出的那一端从前会悄悄变成中灰——节点那圈环就是这么发闷的。 */
describe("mix", () => {
it("mixes two hex colours", () => {
expect(mix("#000000", "#ffffff", 0.5)).toBe("rgb(128,128,128)");
});

it("takes rgb() on either end, the way a token reads back", () => {
// 红 → 白,一半:认得出 rgba 才会是 255,128,128;认不出就退成 192,64,64
expect(mix("#ff0000", "rgba(255,255,255,1)", 0.5)).toBe("rgb(255,128,128)");
expect(mix("rgb(255,0,0)", "#ffffff", 0.5)).toBe("rgb(255,128,128)");
});

it("mixes toward ink on paper, not toward grey", () => {
// 浅色下 INK 是 rgb(23,23,23):绿往墨里走三成,绿该变暗而不是发灰
const ring = mix("#4ea172", "rgb(23,23,23)", 0.35);
expect(ring).toBe("rgb(59,113,82)");
});
});
9 changes: 7 additions & 2 deletions web/src/pages/graphVisuals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ export function hexToRgb(hex: string): [number, number, number] {
}

/** c1 向 c2 按 t 比例混色 */
/** 两色按比例混。**两端都走 `parseRgba`,不是 `hexToRgb`**:令牌读回来的值
* 是 `rgb(23,23,23)` 这种写法,用只认 `#rrggbb` 的解析器会静静地退回中灰
* (`hexToRgb` 认不出就返回 128,128,128),于是 `mix(类型色, INK, t)` 混的
* 不是墨色而是一团灰——选中与悬停的那圈环因此在两个主题里都发闷。
* 从前 `INK` 是字面量 "#ffffff" 才没露馅,0038 把它改成从令牌读之后才显出来。 */
export function mix(c1: string, c2: string, t: number): string {
const [r1, g1, b1] = hexToRgb(c1);
const [r2, g2, b2] = hexToRgb(c2);
const [r1, g1, b1] = parseRgba(c1);
const [r2, g2, b2] = parseRgba(c2);
const f = (a: number, b: number) => Math.round(a + (b - a) * t);
return `rgb(${f(r1, r2)},${f(g1, g2)},${f(b1, b2)})`;
}
Expand Down
Loading