diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 1cb6d54..b0722ad 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -181,7 +181,7 @@ flowchart TD LC --> TM["See You Tomorrow Channel"] ``` -`HoloLakeHome` owns only transient navigation state. It does not persist system ownership, server addresses, or credentials. The existing vault remains the filesystem source of truth, and entering the knowledge module returns to the normal Tolaria workspace. A real server connection may later back a system card without changing that card's logical parent. +`HoloLakeHome` owns only transient navigation state. It does not persist system ownership, server addresses, or credentials. The existing vault remains the filesystem source of truth. From 0.1.7 onward, channel mode owns the full application body; entering Knowledge Lake reveals Tolaria's sidebar and note list as navigation inside that module while a Guanghu module dock remains present. Server registration cards expose stable node identities and future monitoring boundaries without presenting placeholder telemetry as live state. See [ADR 0154](adr/0154-channel-owned-workspace-shell.md). ```mermaid flowchart TD diff --git a/docs/adr/0154-channel-owned-workspace-shell.md b/docs/adr/0154-channel-owned-workspace-shell.md new file mode 100644 index 0000000..fcdcecd --- /dev/null +++ b/docs/adr/0154-channel-owned-workspace-shell.md @@ -0,0 +1,21 @@ +# ADR 0154: Channel-owned workspace shell + +## Status + +Accepted for the 0.1.7 internal prototype. + +## Context + +The first Fifth Domain prototype rendered channel navigation inside Tolaria's editor while leaving Tolaria's sidebar and note list visible. That made the knowledge base and Guanghu channels appear to be two independent products. + +## Decision + +- Channel mode owns the full application body and hides Tolaria's knowledge navigation. +- The channel rail is the primary navigation for domains, human systems, persona paths, modules, and registered servers. +- Opening Knowledge Lake changes the workspace module while preserving a Guanghu module dock. Tolaria's sidebar and note list are subordinate navigation inside that module. +- Server cards expose stable identity and monitoring interface boundaries before live probes are connected. +- Guanghu companion themes expand through semantic color tokens rather than component-local colors. + +## Consequences + +The 0.1.7 prototype establishes ownership and extension points, but it does not yet provide live server telemetry or a fully embedded video application. Those modules must attach to the same workspace contract rather than create parallel shells. diff --git a/package.json b/package.json index 049acd8..3c49f3d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tolaria", "private": true, "license": "AGPL-3.0-or-later", - "version": "0.1.6", + "version": "0.1.7", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 92243f2..784c47c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5547,7 +5547,7 @@ dependencies = [ [[package]] name = "tolaria" -version = "0.1.6" +version = "0.1.7" dependencies = [ "base64 0.22.1", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 523ef72..feb301f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tolaria" -version = "0.1.6" +version = "0.1.7" description = "Personal knowledge and life management app" authors = ["Luca Rossi"] license = "AGPL-3.0-or-later" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3851859..b443545 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "HoloLake Era", - "version": "0.1.6", + "version": "0.1.7", "identifier": "com.guanghu.desktop", "build": { "frontendDist": "../dist", diff --git a/src/App.css b/src/App.css index 68786e4..0ed902b 100644 --- a/src/App.css +++ b/src/App.css @@ -20,12 +20,26 @@ .hololake-bar__library:hover { background: var(--state-hover-subtle); color: var(--text-primary); } .hololake-bar__theme { padding: 5px 9px; border: 1px solid var(--border-default); border-radius: 6px; background: transparent; color: var(--text-secondary); font-size: 12px; cursor: pointer; } .hololake-bar__theme:hover { background: var(--state-hover-subtle); color: var(--text-primary); } +.channel-dock { display: flex; width: 58px; flex: 0 0 58px; flex-direction: column; align-items: center; gap: 8px; padding: 10px 7px; border-right: 1px solid var(--border-default); background: color-mix(in srgb, var(--surface-sidebar) 92%, var(--accent-blue)); } +.channel-dock button { display: grid; width: 44px; min-height: 48px; place-items: center; gap: 2px; padding: 5px 2px; border: 1px solid transparent; border-radius: 9px; background: transparent; color: var(--text-secondary); cursor: pointer; }.channel-dock button:hover, .channel-dock button[data-active="true"] { border-color: var(--border-default); background: var(--state-selected); color: var(--text-primary); }.channel-dock button:disabled { opacity: .45; cursor: default; }.channel-dock span { font-family: Georgia, serif; font-size: 16px; }.channel-dock small { font-size: 9px; } /* Tauri's macOS traffic lights are overlaid on the title bar. Keep the product mark and title in a separate, stable lane so they never collide. */ body.mac-chrome .hololake-bar { padding-left: 92px; } .hololake-bar__brand { min-width: max-content; white-space: nowrap; } .hololake-bar__context { white-space: nowrap; } -.hololake-home { position: relative; z-index: 2; width: 100%; overflow: auto; padding: clamp(28px, 5vw, 64px); color: var(--text-primary); background: var(--surface-app); } +.hololake-home { position: relative; z-index: 2; display: flex; width: 100%; min-width: 0; overflow: hidden; color: var(--text-primary); background: var(--surface-app); } +.channel-workspace { min-width: 0; flex: 1; overflow: auto; padding: clamp(28px, 5vw, 64px); } +.channel-rail { display: flex; width: 236px; flex: 0 0 236px; flex-direction: column; gap: 22px; overflow-y: auto; padding: 22px 14px; border-right: 1px solid var(--border-default); background: color-mix(in srgb, var(--surface-sidebar) 94%, var(--accent-blue)); } +.channel-rail__heading { display: grid; gap: 5px; padding: 4px 10px 14px; border-bottom: 1px solid var(--border-default); } +.channel-rail__heading span { color: var(--accent-blue); font: 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .18em; } +.channel-rail__heading strong { font-size: 17px; }.channel-rail__heading small { color: var(--text-muted); font-size: 11px; line-height: 1.5; } +.channel-rail nav { display: grid; gap: 4px; } +.channel-rail nav button { justify-content: flex-start; width: 100%; height: 36px; padding-inline: 10px; color: var(--text-secondary); } +.channel-rail nav button[data-active="true"] { background: var(--state-selected); color: var(--text-primary); } +.channel-rail nav i { width: 8px; height: 8px; border-radius: 999px; background: var(--tag-color); box-shadow: 0 0 10px color-mix(in srgb, var(--tag-color) 55%, transparent); } +.channel-rail nav i[data-color="cyan"] { --tag-color: #4fd1c5; }.channel-rail nav i[data-color="blue"] { --tag-color: #60a5fa; }.channel-rail nav i[data-color="violet"] { --tag-color: #a78bfa; }.channel-rail nav i[data-color="amber"] { --tag-color: #fbbf24; }.channel-rail nav i[data-color="rose"] { --tag-color: #fb7185; }.channel-rail nav i[data-color="pink"] { --tag-color: #f472b6; }.channel-rail nav i[data-color="green"] { --tag-color: #4ade80; } +.channel-rail__modules { display: grid; gap: 8px; margin-top: auto; padding: 14px 10px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-card); } +.channel-rail__modules span { color: var(--text-muted); font-size: 10px; letter-spacing: .12em; }.channel-rail__modules b { font-size: 12px; font-weight: 550; } .hololake-home__hero { position: relative; max-width: 740px; padding: 24px 0 56px; overflow: hidden; } .hololake-home__halo { position: absolute; right: -80px; top: -140px; width: 300px; aspect-ratio: 1; border: 1px solid var(--border-default); border-radius: 50%; opacity: .45; } .hololake-home__eyebrow, .hololake-home__section-head span { color: var(--accent-blue); font-size: 12px; letter-spacing: .12em; text-transform: uppercase; } @@ -60,6 +74,10 @@ body.mac-chrome .hololake-bar { padding-left: 92px; } .channel-card button { align-self: flex-start; margin-top: 20px; padding: 0; color: var(--accent-blue); } .channel-card__status { margin-top: 20px; color: var(--text-muted); font-size: 12px; } .channel-stage__tools { display: flex; gap: 10px; margin-top: 18px; } +.server-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; } +.server-card { padding: 20px; border: 1px solid var(--border-default); border-radius: 12px; background: var(--surface-card); } +.server-card__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }.server-card__head code { color: var(--accent-blue); font-size: 11px; }.server-card__head span { padding: 3px 7px; border-radius: 999px; background: var(--surface-panel); color: var(--text-muted); font-size: 10px; }.server-card__head span[data-tone="online"] { background: color-mix(in srgb, var(--accent-green) 18%, transparent); color: var(--accent-green); } +.server-card h2 { margin: 28px 0 6px; font-size: 18px; }.server-card > p { margin: 0 0 22px; color: var(--text-secondary); font-size: 12px; }.server-card dl { display: grid; gap: 8px; margin: 0 0 20px; }.server-card dl div { display: flex; justify-content: space-between; gap: 12px; font-size: 11px; }.server-card dt { color: var(--text-muted); }.server-card dd { margin: 0; color: var(--text-secondary); }.server-card button { width: 100%; font-size: 11px; } .persona-route-list { display: grid; gap: 12px; max-width: 820px; margin: 0; padding: 0; list-style: none; } .persona-route-list li { display: grid; grid-template-columns: 140px 1fr auto; align-items: center; gap: 18px; padding: 20px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-card); } .persona-route-list code { color: var(--accent-blue); font-size: 11px; } @@ -72,8 +90,9 @@ body.mac-chrome .hololake-bar { padding-left: 92px; } .guanghu-architecture__routes code { align-self: start; color: var(--accent-blue); font-size: 11px; } .guanghu-architecture__routes strong { font-size: 14px; }.guanghu-architecture__routes p { margin: 5px 0 0; color: var(--text-secondary); font-size: 12px; line-height: 1.65; } .guanghu-architecture__route { overflow-x: auto; margin: 2px 0 0; padding: 12px; border-radius: 6px; color: var(--text-secondary); background: var(--surface-app); font: 11px/1.6 monospace; white-space: nowrap; } -@media (max-width: 900px) { .channel-grid--three { grid-template-columns: 1fr; }.channel-card { min-height: 190px; } } -@media (max-width: 780px) { .hololake-home__signal-grid, .hololake-home__module-grid, .channel-grid { grid-template-columns: 1fr; }.hololake-bar__context { display: none; }.hololake-home { padding: 28px; }.channel-breadcrumb { overflow-x: auto; }.channel-stage h1 { font-size: 42px; } } +@media (max-width: 1000px) { .server-grid { grid-template-columns: 1fr; } } +@media (max-width: 900px) { .channel-grid--three { grid-template-columns: 1fr; }.channel-card { min-height: 190px; }.channel-rail { width: 190px; flex-basis: 190px; } } +@media (max-width: 780px) { .hololake-home__signal-grid, .hololake-home__module-grid, .channel-grid { grid-template-columns: 1fr; }.hololake-bar__context { display: none; }.channel-workspace { padding: 28px; }.channel-rail { display: none; }.channel-breadcrumb { overflow-x: auto; }.channel-stage h1 { font-size: 42px; } } @media (max-width: 700px) { body.mac-chrome .hololake-bar { padding-left: 84px; }.hololake-bar__line { display: none; } } .app { diff --git a/src/App.tsx b/src/App.tsx index e609fde..08eb639 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1635,12 +1635,19 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
- 光湖语言人格驱动操作系统 - + {showHoloLakeHome ? '光湖语言人格驱动操作系统' : '永恒湖心系统 · 知识湖模块'}
- {sidebarVisible && ( + {!showHoloLakeHome && noteWindowParams === null && ( + + )} + {sidebarVisible && !showHoloLakeHome && ( <>
@@ -1648,7 +1655,7 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu )} - {noteListVisible && ( + {noteListVisible && !showHoloLakeHome && ( <>
{effectiveSelection.kind === 'filter' && effectiveSelection.filter === 'pulse' ? ( diff --git a/src/components/HoloLakeHome.test.tsx b/src/components/HoloLakeHome.test.tsx index 361aabb..846e717 100644 --- a/src/components/HoloLakeHome.test.tsx +++ b/src/components/HoloLakeHome.test.tsx @@ -17,21 +17,21 @@ describe('HoloLakeHome', () => { render() expect(screen.getByRole('heading', { name: '零点原核频道' })).toBeInTheDocument() - expect(screen.queryByText('爱之核心子系统')).not.toBeInTheDocument() + expect(screen.queryByRole('heading', { name: '爱之核心子系统' })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) expect(screen.getByRole('heading', { name: '第五域' })).toBeInTheDocument() expect(screen.getByText('胖头鱼语言子系统')).toBeInTheDocument() - expect(screen.getByText('永恒湖心系统')).toBeInTheDocument() - expect(screen.queryByText('爱之核心子系统')).not.toBeInTheDocument() + expect(screen.getByRole('heading', { name: '永恒湖心系统' })).toBeInTheDocument() + expect(screen.queryByRole('heading', { name: '爱之核心子系统' })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) expect(screen.getByRole('heading', { name: '永恒湖心系统' })).toBeInTheDocument() - expect(screen.getByText('光之湖子系统')).toBeInTheDocument() - expect(screen.getByText('心跳核心频道')).toBeInTheDocument() - expect(screen.getByText('爱之核心子系统')).toBeInTheDocument() + expect(screen.getByRole('heading', { name: '光之湖子系统' })).toBeInTheDocument() + expect(screen.getByRole('heading', { name: '心跳核心频道' })).toBeInTheDocument() + expect(screen.getByRole('heading', { name: '爱之核心子系统' })).toBeInTheDocument() }) it('keeps Zhizhi inside Eternal Lake Heart and routes to Tomorrow channel', () => { @@ -69,6 +69,17 @@ describe('HoloLakeHome', () => { expect(screen.getByText('霜砚人格系统路径')).toBeInTheDocument() }) + it('keeps a server registry interface inside the Heartbeat Core workspace', () => { + render() + + fireEvent.click(screen.getByRole('button', { name: '服务器与节点' })) + + expect(screen.getByRole('heading', { name: '服务器与灯塔节点' })).toBeInTheDocument() + expect(screen.getByText('BS-SG-001')).toBeInTheDocument() + expect(screen.getByText('JD-FD-PRIMARY')).toBeInTheDocument() + expect(screen.getAllByRole('button', { name: /接口已预留/ })).toHaveLength(3) + }) + it('opens the public domestic development repository', () => { render() diff --git a/src/components/HoloLakeHome.tsx b/src/components/HoloLakeHome.tsx index 5ce5c3b..414e81e 100644 --- a/src/components/HoloLakeHome.tsx +++ b/src/components/HoloLakeHome.tsx @@ -20,7 +20,7 @@ type HoloLakeHomeProps = { onEnterKnowledgeBase: () => void } -type ChannelRoute = 'zero-core' | 'fifth-domain' | 'eternal-lake-heart' | 'light-lake' | 'heartbeat-core' | 'love-core' +type ChannelRoute = 'zero-core' | 'fifth-domain' | 'eternal-lake-heart' | 'light-lake' | 'heartbeat-core' | 'love-core' | 'servers' type RouteCardProps = { action?: string @@ -56,6 +56,12 @@ function PersonaRoute({ id, title }: { id: string; title: string }) { return
  • {id}{title}
  • } +const registeredServers = [ + { id: 'BS-SG-001', label: '新加坡大脑服务器', owner: '冰朔 · 永恒湖心', status: '已接入', tone: 'online' }, + { id: 'BS-GZ-001', label: '广州个人服务器', owner: '冰朔 · 心跳核心', status: '接口预留', tone: 'pending' }, + { id: 'JD-FD-PRIMARY', label: '第五域企业主节点', owner: '第五域 · 零点原核', status: '接口预留', tone: 'pending' }, +] as const + export function HoloLakeHome({ locale, onEnterKnowledgeBase }: HoloLakeHomeProps) { const [route, setRoute] = useState('zero-core') const [architectureOpen, setArchitectureOpen] = useState(false) @@ -143,6 +149,27 @@ export function HoloLakeHome({ locale, onEnterKnowledgeBase }: HoloLakeHomeProps ) } + if (route === 'servers') { + return ( +
    +

    SERVER REGISTRY · READ ONLY

    +

    服务器与灯塔节点

    +

    频道先登记服务器身份、归属和接口位置;实时运行数据将在节点探针接入后显示。

    +
    + {registeredServers.map(server => ( +
    +
    {server.id}{server.status}
    +

    {server.label}

    +

    {server.owner}

    +
    运行状态
    等待真实探针
    最后心跳
    尚未接入
    + +
    + ))} +
    +
    + ) + } + return (

    {t('hololake.channel.heartbeatTitle')}

    @@ -151,6 +178,7 @@ export function HoloLakeHome({ locale, onEnterKnowledgeBase }: HoloLakeHomeProps + navigate('servers')} />
    @@ -162,13 +190,28 @@ export function HoloLakeHome({ locale, onEnterKnowledgeBase }: HoloLakeHomeProps return (
    - + - {renderRoute()} +
    + + {renderRoute()} +
    diff --git a/src/hooks/useGuanghuTheme.ts b/src/hooks/useGuanghuTheme.ts index ec175ce..aeccf90 100644 --- a/src/hooks/useGuanghuTheme.ts +++ b/src/hooks/useGuanghuTheme.ts @@ -12,7 +12,7 @@ function applyGuanghuTheme(theme: GuanghuTheme): void { document.documentElement.setAttribute('data-guanghu-theme', theme) if (theme === 'native') return - const mode = theme === 'lake-light' ? 'light' : 'dark' + const mode = ['lake-light', 'dawn-gold', 'tundra-green'].includes(theme) ? 'light' : 'dark' applyThemeModeToDocument(document, mode) writeStoredThemeMode(window.localStorage, mode) } diff --git a/src/index.css b/src/index.css index 30a010c..c65f27a 100644 --- a/src/index.css +++ b/src/index.css @@ -400,6 +400,70 @@ --border-focus: #8bc9e5; } +[data-guanghu-theme="aurora-dark"][data-theme="dark"] { + --surface-app: #07151b; + --surface-sidebar: #0b2028; + --surface-panel: #102b34; + --surface-card: #143640; + --surface-popover: #183f49; + --surface-editor: #07151b; + --text-primary: #e6fbfb; + --text-secondary: #9fc7c9; + --accent-blue: #4ee2d0; + --accent-blue-hover: #80f1e4; + --accent-blue-bg: rgba(78, 226, 208, .15); + --state-selected: #16484d; + --state-selected-strong: #1d5a5f; + --border-focus: #4ee2d0; +} + +[data-guanghu-theme="yaoming-purple"][data-theme="dark"] { + --surface-app: #15101d; + --surface-sidebar: #1d1629; + --surface-panel: #251c33; + --surface-card: #2d2340; + --surface-popover: #362a4b; + --surface-editor: #15101d; + --text-primary: #f2eaff; + --text-secondary: #bdacd3; + --accent-blue: #b88cff; + --accent-blue-hover: #d0b2ff; + --accent-blue-bg: rgba(184, 140, 255, .16); + --state-selected: #44315f; + --state-selected-strong: #563c78; + --border-focus: #b88cff; +} + +[data-guanghu-theme="dawn-gold"][data-theme="light"] { + --surface-app: #fffaf0; + --surface-sidebar: #f8f0df; + --surface-panel: #fffdf8; + --surface-card: #fff9eb; + --surface-popover: #fffdf8; + --surface-editor: #fffdf8; + --accent-blue: #a66a16; + --accent-blue-hover: #7f4f0d; + --accent-blue-bg: rgba(196, 132, 37, .14); + --state-selected: #f5e4bd; + --state-selected-strong: #ecd39c; + --border-focus: #b6781d; +} + +[data-guanghu-theme="tundra-green"][data-theme="light"] { + --surface-app: #f4f8f1; + --surface-sidebar: #eaf1e5; + --surface-panel: #fbfdf9; + --surface-card: #f7fbf4; + --surface-popover: #fbfdf9; + --surface-editor: #fbfdf9; + --accent-blue: #4f7b48; + --accent-blue-hover: #385f33; + --accent-blue-bg: rgba(79, 123, 72, .14); + --state-selected: #dcebd6; + --state-selected-strong: #cbe0c3; + --border-focus: #4f7b48; +} + /* --- Tailwind v4 theme inline: register colors + radii --- */ @theme inline { --color-background: var(--background); diff --git a/src/lib/guanghuTheme.test.ts b/src/lib/guanghuTheme.test.ts index 6843fd2..ed5a82a 100644 --- a/src/lib/guanghuTheme.test.ts +++ b/src/lib/guanghuTheme.test.ts @@ -6,7 +6,7 @@ import { describe('guanghuTheme', () => { it('keeps the companion theme choices explicit and stable', () => { - expect(GUANGHU_THEMES).toEqual(['native', 'lake-light', 'lake-dark']) + expect(GUANGHU_THEMES).toEqual(['native', 'lake-light', 'lake-dark', 'aurora-dark', 'yaoming-purple', 'dawn-gold', 'tundra-green']) }) it('falls back to the native Tolaria theme for unknown values', () => { @@ -17,5 +17,7 @@ describe('guanghuTheme', () => { it('accepts every supported companion theme', () => { expect(normalizeGuanghuTheme('lake-light')).toBe('lake-light') expect(normalizeGuanghuTheme('lake-dark')).toBe('lake-dark') + expect(normalizeGuanghuTheme('yaoming-purple')).toBe('yaoming-purple') + expect(normalizeGuanghuTheme('dawn-gold')).toBe('dawn-gold') }) }) diff --git a/src/lib/guanghuTheme.ts b/src/lib/guanghuTheme.ts index 7ed2806..c5bf564 100644 --- a/src/lib/guanghuTheme.ts +++ b/src/lib/guanghuTheme.ts @@ -1,12 +1,16 @@ export const GUANGHU_THEME_STORAGE_KEY = 'guanghu-theme' -export const GUANGHU_THEMES = ['native', 'lake-light', 'lake-dark'] as const +export const GUANGHU_THEMES = ['native', 'lake-light', 'lake-dark', 'aurora-dark', 'yaoming-purple', 'dawn-gold', 'tundra-green'] as const export type GuanghuTheme = typeof GUANGHU_THEMES[number] export const GUANGHU_THEME_LABELS: Record = { native: '跟随原生', 'lake-light': '湖光浅色(亮)', 'lake-dark': '湖心深色(暗)', + 'aurora-dark': '极光深海', + 'yaoming-purple': '曜冥紫', + 'dawn-gold': '晨曦金', + 'tundra-green': '苔原绿', } export function normalizeGuanghuTheme(value: unknown): GuanghuTheme {