diff --git a/engineering/MODULE-REGISTRY.json b/engineering/MODULE-REGISTRY.json index 00446a6..0aad527 100644 --- a/engineering/MODULE-REGISTRY.json +++ b/engineering/MODULE-REGISTRY.json @@ -1,7 +1,7 @@ { "schema": "hololake.module-registry/v1", "registry_id": "HL-MODULE-REGISTRY-0001", - "version": "2026.08.07.1", + "version": "2026.08.07.2", "authority_rule": "A module entry maps a stable id to a real path and provenance; it grants no execution authority. Pulling a module does not equal being online.", "modules": [ { @@ -31,6 +31,90 @@ }, "reuse_policy": "CANDIDATE_ELSEWHERE: other personas must run scripts/drill.sh and reconcile detected counts with human-observed events before claiming online.", "recorded_at": "2026-08-07T16:00:00+08:00" + }, + { + "id": "HL-MOD-KNOWLEDGE-001", + "name": "hololake-knowledge-lake", + "version": "0.4.6", + "state": "MOUNTED_STAGE_1", + "path": "product-source/hololake-platform/src/", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "Tolaria REPO-004 component source, assembled in REPO-014", + "license": "AGPL-3.0-or-later", + "capabilities": ["knowledge.search", "knowledge.read", "knowledge.edit", "knowledge.project"], + "execution_boundary": "application-scoped" + }, + { + "id": "HL-MOD-DOC-001", + "name": "blocknote-document-surface", + "version": "0.46.2", + "state": "MOUNTED_STAGE_1", + "path": "product-source/hololake-platform/src/components/SingleEditorView.tsx", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "BlockNote via Tolaria REPO-004", + "license": "MPL-2.0 majority; verify per package before redistribution", + "capabilities": ["document.open", "document.edit", "document.persist"], + "execution_boundary": "application-scoped" + }, + { + "id": "HL-MOD-SHEET-001", + "name": "ironcalc-spreadsheet-surface", + "version": "0.5.7", + "state": "MOUNTED_STAGE_1", + "path": "product-source/hololake-platform/src/components/SheetEditor.tsx", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "IronCalc via Tolaria REPO-004", + "license": "MIT OR Apache-2.0", + "capabilities": ["sheet.open", "sheet.edit", "sheet.calculate", "sheet.persist"], + "execution_boundary": "application-scoped" + }, + { + "id": "HL-MOD-CHART-001", + "name": "education-comparison-chart", + "version": "1.0.0", + "state": "MOUNTED_STAGE_1", + "path": "product-source/hololake-platform/src/components/EducationWorkLake.tsx", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "HoloLake native client projection", + "license": "AGPL-3.0-or-later", + "capabilities": ["chart.project"], + "execution_boundary": "read-only-projection-of-application-data" + }, + { + "id": "HL-MOD-DASHBOARD-001", + "name": "education-dashboard", + "version": "1.0.0", + "state": "MOUNTED_STAGE_1", + "path": "product-source/hololake-platform/src/components/EducationWorkLake.tsx", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "HoloLake native client projection", + "license": "AGPL-3.0-or-later", + "capabilities": ["dashboard.project"], + "execution_boundary": "read-only-projection-of-application-data" + }, + { + "id": "HL-MOD-CANVAS-001", + "name": "whiteboard-canvas-candidate", + "version": "unselected", + "state": "QUARANTINE_LICENSE_REVIEW", + "path": "product-source/hololake-platform/src/components/TldrawWhiteboard.tsx", + "repo": "REPO-014 hololake-system-architecture", + "provenance": "tldraw component inherited through Tolaria REPO-004; Excalidraw is the permissive replacement candidate", + "license": "tldraw production license required; Excalidraw MIT candidate", + "capabilities": [], + "execution_boundary": "not-registered-for-production-execution" + }, + { + "id": "HL-MOD-AGENT-RUNTIME-001", + "name": "coding-agent-pattern-study", + "version": "780d1388fff103ff0db0d8c14de65af6225b4860", + "state": "RESEARCH_ONLY_ISOLATED", + "path": "/Volumes/JZAO/HoloLake/upstream-mirrors/grok-build-upstream.git", + "repo": "REPO-015 grok-build-upstream-mirror", + "provenance": "SpaceXAI Grok Build mirror, Apache-2.0 first-party code", + "license": "Apache-2.0 plus third-party notices", + "capabilities": [], + "execution_boundary": "patterns-may-be-reimplemented; never merge runtime into persona subject" } ] } diff --git a/product-source/hololake-platform/docs/ABSTRACTIONS.md b/product-source/hololake-platform/docs/ABSTRACTIONS.md index 24c05ca..2db74ad 100644 --- a/product-source/hololake-platform/docs/ABSTRACTIONS.md +++ b/product-source/hololake-platform/docs/ABSTRACTIONS.md @@ -172,6 +172,16 @@ interface VaultEntry { } ``` +### Industry work lake + +An industry work lake is a client-rendered collection of registered capabilities rather than a new vault type. The stage-one education channel uses: + +- `EducationInstructionRoute` to map one bounded language instruction to one surface and module id. +- `EducationWorkLake` to hold application-scoped document and spreadsheet state and project chart/dashboard views. +- `engineering/MODULE-REGISTRY.json` as the stable lookup from module id to real source path, provenance, license state, and execution boundary. + +Opening a surface proves only that the client routed and rendered it. It does not prove server persistence, multi-user collaboration, deployment health, or persona birth. + ### WorkspaceIdentity Mounted workspace provenance is renderer-owned metadata attached to `VaultEntry.workspace` when entries are loaded through the registered workspace set. It is not parsed from note frontmatter and is not written into vault files. diff --git a/product-source/hololake-platform/docs/ARCHITECTURE.md b/product-source/hololake-platform/docs/ARCHITECTURE.md index 801f35b..bf1e0d6 100644 --- a/product-source/hololake-platform/docs/ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/ARCHITECTURE.md @@ -157,7 +157,7 @@ Editor responsiveness is also protected by a synthetic browser benchmark. `pnpm | Backend language | Rust (edition 2021) | 1.77.2 | | Frontmatter parsing | gray_matter | 0.2 | | Filesystem watcher | notify | 6.1 | -| AI (workspace) | CLI agent adapters (Claude Code + Codex + GitHub Copilot + OpenCode + Pi + Antigravity + Kiro + Hermes Agent) plus configured local/API model targets | - | +| AI (workspace) | Product-visible CLI adapters (Claude Code + Codex + GitHub Copilot) plus configured local/API model targets | - | | Search | Keyword (walkdir-based file scan) | - | | Localization | App-owned runtime + JSON catalogs (`src/lib/i18n.ts`, `src/lib/locales/*.json`, `lara.yaml`) | English fallback + Lara CLI sync | | MCP | @modelcontextprotocol/sdk | 1.0 | @@ -183,6 +183,8 @@ flowchart TD `HoloLakeHome` owns only transient navigation state. It does not persist system ownership, server addresses, or credentials. Its navigation and native-module entries now pass through the versioned living-system contract in `guanghuLivingSystem.ts`. Each event binds one exact allowlisted intent; the host invokes Knowledge Lake, Agent workspace, or local-folder callbacks only after the returned event id, intent, route, truth references, authorization state, and declarative scene all validate. Theme selection remains a human decision and emits only a bounded scene-adaptation intent. The native desktop first requests a receipt-backed plan from the BS-SH-005 lighthouse through a loopback-only HLDP tunnel; when that tunnel is absent the native command restores the exact registered SSH forward before retrying once. The DeepSeek credential never leaves the server. A configured direct model is the secondary planner, and the deterministic recovery plan is the final availability path. Invalid, unavailable, or late model output produces an explicitly tagged recovery plan rather than an invented success. The canvas consumes only validated motion, star-density, depth, and connection-emphasis primitives; the model never draws pixels or runs per animation frame. 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 the sidebar and note list as navigation inside that module while a Guanghu module dock remains present. In 0.1.8, `JD-FD-PRIMARY` resolves the public Fifth Domain discovery document without credentials and validates its read-only Guanghu routes. That result proves only public navigation availability; the UI continues to withhold private server-health claims until an authenticated probe exists. See [ADR 0154](adr/0154-channel-owned-workspace-shell.md), [ADR 0155](adr/0155-fifth-domain-read-only-discovery-and-ai-tool-boundary.md), and [ADR 0169](adr/0169-model-native-living-galaxy-system.md). +The education work lake is the first remote-first industry channel. `EducationWorkLake` routes a bounded language instruction to one registered capability, opens a client-rendered surface, and exposes the capability and execution receipt. Its editable document and IronCalc spreadsheet remain inside the application boundary; chart and dashboard views are projections of the current sheet. The HoloLake world entry bypasses local-vault onboarding while the existing local knowledge lake remains available as an optional module. See [ADR 0173](adr/0173-remote-first-industry-work-lakes.md). + ```mermaid flowchart TD subgraph TW["Tauri v2 Window"] diff --git a/product-source/hololake-platform/docs/adr/0173-remote-first-industry-work-lakes.md b/product-source/hololake-platform/docs/adr/0173-remote-first-industry-work-lakes.md new file mode 100644 index 0000000..fcd12a0 --- /dev/null +++ b/product-source/hololake-platform/docs/adr/0173-remote-first-industry-work-lakes.md @@ -0,0 +1,27 @@ +--- +type: ADR +id: "0173" +title: "Remote-first industry work lakes use registered client-rendered modules" +status: active +date: 2026-08-07 +--- + +## Context + +The inherited Tolaria entry assumes that a local filesystem vault must exist before any useful surface can open. HoloLake needs a different product boundary: Git repositories hold reusable, numbered components; a persona routes human language to those components; the resulting document, spreadsheet, chart, dashboard, or knowledge surface renders on the user's device. An industry workspace may be temporarily hosted on `JD-FD-PRIMARY` and later moved without changing its capability identifiers. + +## Decision + +Industry work lakes are remote-first application channels above the optional local vault. + +- The HoloLake world entry is available without creating, choosing, or repairing a local vault. +- Every callable surface has a stable module id in `engineering/MODULE-REGISTRY.json`. +- Language routing selects a registered capability and emits a receipt; it does not treat model text as proof of execution. +- Stage-one document and spreadsheet state remains inside the application/browser boundary. +- Server hosting and client rendering are separate: the temporary server serves the application, while user-facing editing and projection execute on the client. +- The first education work lake mounts document, spreadsheet, comparison-chart, dashboard, and knowledge surfaces. Remote multi-user persistence is a later capability and must have its own authorization and receipt contract. +- Grok Build remains an isolated research source for agent-runtime patterns. Its runtime is not merged into a persona subject. + +## Consequences + +The obsolete local-vault chooser no longer blocks the HoloLake entry. Existing vault features remain available as one optional knowledge module. Industry migration becomes a deployment-route change rather than a data-model rewrite. Collaborative persistence, domestic model providers, and production whiteboards remain explicit follow-up modules instead of hidden claims in stage one. diff --git a/product-source/hololake-platform/package.json b/product-source/hololake-platform/package.json index 7e5561a..02f376e 100644 --- a/product-source/hololake-platform/package.json +++ b/product-source/hololake-platform/package.json @@ -10,6 +10,7 @@ "prebuild": "pnpm test:native-authority && pnpm test:native-core", "build": "tsc -b && vite build", "build:team-foundation": "VITE_HOLOLAKE_DISTRIBUTION=team-foundation tsc -b && VITE_HOLOLAKE_DISTRIBUTION=team-foundation vite build && node scripts/finalize-team-foundation-build.mjs", + "build:education": "tsc -b && vite build --base=/jd/education/", "agent-docs": "node scripts/build-agent-docs.mjs", "bundle-mcp": "node scripts/bundle-mcp-server.mjs", "docs:dev": "vitepress dev site --host 127.0.0.1", diff --git a/product-source/hololake-platform/scripts/validate-locales.mjs b/product-source/hololake-platform/scripts/validate-locales.mjs index 3b5e906..4eec7e6 100644 --- a/product-source/hololake-platform/scripts/validate-locales.mjs +++ b/product-source/hololake-platform/scripts/validate-locales.mjs @@ -112,7 +112,8 @@ assertFlatStringCatalog('en', sourceCatalog) const sourceKeys = Object.keys(sourceCatalog).sort() ensureDirectory(localesDir) -const localeFiles = directoryFiles(localesDir).filter((file) => file.endsWith('.json')) +const localeFiles = directoryFiles(localesDir) + .filter((file) => file.endsWith('.json') && !file.startsWith('._')) const issues = [] for (const file of localeFiles) { diff --git a/product-source/hololake-platform/src/App.tsx b/product-source/hololake-platform/src/App.tsx index 7690453..b39f2e6 100644 --- a/product-source/hololake-platform/src/App.tsx +++ b/product-source/hololake-platform/src/App.tsx @@ -1436,6 +1436,7 @@ function MainApp({ telemetryConsent: settings.telemetry_consent, vaultIsLoading: vault.isLoading, vaultSwitcher, + worldEntryVisible: showHoloLakeHome, }) const deepLinks = useDeepLinks({ activeEntry: activeTab?.entry ?? null, diff --git a/product-source/hololake-platform/src/components/AiAgentsOnboardingPrompt.test.tsx b/product-source/hololake-platform/src/components/AiAgentsOnboardingPrompt.test.tsx index a54e3ee..97a2008 100644 --- a/product-source/hololake-platform/src/components/AiAgentsOnboardingPrompt.test.tsx +++ b/product-source/hololake-platform/src/components/AiAgentsOnboardingPrompt.test.tsx @@ -19,11 +19,6 @@ const installLinkTargets = [ ['ai-agents-onboarding-install-claude_code', 'https://docs.anthropic.com/en/docs/claude-code'], ['ai-agents-onboarding-install-codex', 'https://developers.openai.com/codex/cli'], ['ai-agents-onboarding-install-copilot', 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli'], - ['ai-agents-onboarding-install-opencode', 'https://opencode.ai/docs/'], - ['ai-agents-onboarding-install-pi', 'https://pi.dev'], - ['ai-agents-onboarding-install-antigravity', 'https://antigravity.google/docs/cli/install'], - ['ai-agents-onboarding-install-kiro', 'https://kiro.dev/docs/cli'], - ['ai-agents-onboarding-install-hermes', 'https://hermes-agent.nousresearch.com/docs/getting-started/quickstart'], ] as const vi.mock('../utils/url', () => ({ diff --git a/product-source/hololake-platform/src/components/EducationWorkLake.css b/product-source/hololake-platform/src/components/EducationWorkLake.css new file mode 100644 index 0000000..534c2ae --- /dev/null +++ b/product-source/hololake-platform/src/components/EducationWorkLake.css @@ -0,0 +1,30 @@ +.education-work-lake { display: grid; gap: 20px; width: min(1160px, 100%); margin: 0 auto; padding-bottom: 48px; } +.education-work-lake__header { display: flex; align-items: end; justify-content: space-between; gap: 28px; padding: 24px 0 8px; } +.education-work-lake__header p { margin: 0 0 8px; color: var(--accent-blue); font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .12em; } +.education-work-lake__header h1 { margin: 0; font-family: Georgia, "Songti SC", serif; font-size: clamp(34px, 5vw, 58px); font-weight: 500; letter-spacing: -.04em; } +.education-work-lake__header span { display: block; max-width: 720px; margin-top: 12px; color: var(--text-secondary); line-height: 1.7; } +.education-work-lake__header aside { min-width: 190px; padding: 14px 16px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-card); } +.education-work-lake__header aside i { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--accent-green); } +.education-work-lake__header aside strong, .education-work-lake__header aside small { display: block; margin-top: 4px; } +.education-work-lake__header aside small { color: var(--text-secondary); } +.education-command { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 14px; border: 1px solid color-mix(in srgb, var(--accent-blue) 35%, var(--border-default)); border-radius: 12px; background: color-mix(in srgb, var(--surface-card) 92%, var(--accent-blue)); } +.education-command input { min-height: 42px; } +.education-receipt { display: flex; align-items: center; gap: 12px; min-height: 38px; padding: 8px 12px; border: 1px solid color-mix(in srgb, var(--accent-green) 36%, var(--border-default)); border-radius: 8px; color: var(--text-secondary); background: color-mix(in srgb, var(--surface-card) 94%, var(--accent-green)); } +.education-receipt code { color: var(--accent-green); }.education-receipt small { margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.education-work-lake [data-slot="tabs-list"] { max-width: 100%; overflow-x: auto; } +.education-work-lake [data-slot="tabs-content"] { min-height: 430px; margin-top: 12px; padding: 18px; border: 1px solid var(--border-default); border-radius: 12px; background: var(--surface-card); } +.education-document { min-height: 390px; resize: vertical; font: 16px/1.8 ui-serif, "Songti SC", serif; } +.education-sheet { height: 430px; overflow: hidden; border-radius: 8px; } +.education-chart { display: grid; gap: 18px; padding: 24px 16px; } +.education-chart__row { display: grid; grid-template-columns: 64px minmax(140px, 1fr) 42px; align-items: center; gap: 12px; } +.education-chart__row > div { height: 18px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--surface-muted) 82%, transparent); } +.education-chart__row i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--accent-blue), #8b7cf6); } +.education-dashboard { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; } +.education-dashboard > article { padding: 20px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-app); } +.education-dashboard article span, .education-dashboard article small { display: block; color: var(--text-secondary); } +.education-dashboard article strong { display: block; margin: 12px 0 8px; font-size: 30px; } +.education-dashboard__chart { grid-column: 1 / -1; } +.education-knowledge { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; } +.education-knowledge article { padding: 22px; border: 1px solid var(--border-default); border-radius: 10px; background: var(--surface-app); } +.education-knowledge code, .education-knowledge strong { display: block; }.education-knowledge code { color: var(--accent-blue); font-size: 11px; }.education-knowledge strong { margin-top: 16px; font-size: 17px; }.education-knowledge p { color: var(--text-secondary); line-height: 1.7; } +@media (max-width: 760px) { .education-work-lake__header { align-items: stretch; flex-direction: column; }.education-work-lake__header aside { min-width: 0; }.education-command { grid-template-columns: 1fr; }.education-dashboard, .education-knowledge { grid-template-columns: 1fr; }.education-dashboard__chart { grid-column: auto; }.education-receipt { align-items: flex-start; flex-direction: column; }.education-receipt small { margin-left: 0; } } diff --git a/product-source/hololake-platform/src/components/EducationWorkLake.test.tsx b/product-source/hololake-platform/src/components/EducationWorkLake.test.tsx new file mode 100644 index 0000000..02ed52c --- /dev/null +++ b/product-source/hololake-platform/src/components/EducationWorkLake.test.tsx @@ -0,0 +1,45 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { EducationWorkLake } from './EducationWorkLake' + +vi.mock('./SheetEditor', () => ({ + SheetEditor: () =>
sheet
, +})) + +describe('EducationWorkLake', () => { + beforeEach(() => { + localStorage.clear() + }) + + it('opens an editable sheet from a language instruction and emits a receipt', () => { + render() + + fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), { + target: { value: '整理一下这个表格里面的数据' }, + }) + fireEvent.click(screen.getByRole('button', { name: '运行指令' })) + + expect(screen.getByTestId('education-sheet-editor')).toBeInTheDocument() + expect(screen.getByText('HL-MOD-SHEET-001')).toBeInTheDocument() + expect(screen.getByText(/已在应用内打开/)).toBeInTheDocument() + }) + + it('keeps a document draft inside the browser workspace', () => { + const { unmount } = render() + fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), { + target: { value: '起草一份教研文档' }, + }) + fireEvent.click(screen.getByRole('button', { name: '运行指令' })) + fireEvent.change(screen.getByLabelText('教研文档内容'), { + target: { value: '八年级物理:光的折射' }, + }) + unmount() + + render() + fireEvent.change(screen.getByLabelText('告诉人格体你要处理什么'), { + target: { value: '打开文档' }, + }) + fireEvent.click(screen.getByRole('button', { name: '运行指令' })) + expect(screen.getByLabelText('教研文档内容')).toHaveValue('八年级物理:光的折射') + }) +}) diff --git a/product-source/hololake-platform/src/components/EducationWorkLake.tsx b/product-source/hololake-platform/src/components/EducationWorkLake.tsx new file mode 100644 index 0000000..4db9b4f --- /dev/null +++ b/product-source/hololake-platform/src/components/EducationWorkLake.tsx @@ -0,0 +1,185 @@ +import { useMemo, useState } from 'react' +import type { AppLocale, TranslationKey } from '../lib/i18n' +import { translate } from '../lib/i18n' +import { + educationMetricsFromSheet, + routeEducationInstruction, + type EducationSurface, +} from '../lib/educationWorkLake' +import { trackEvent } from '../lib/telemetry' +import { Button } from './ui/button' +import { Input } from './ui/input' +import { Textarea } from './ui/textarea' +import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs' +import { SheetEditor } from './SheetEditor' +import './EducationWorkLake.css' + +const DOCUMENT_STORAGE_KEY = 'hololake:education-work-lake:document' +const SHEET_STORAGE_KEY = 'hololake:education-work-lake:sheet' +const DEFAULT_DOCUMENT = '教研主题:八年级物理学情分析\n\n在这里编辑教案、报告或协作文档。' +const DEFAULT_SHEET = [ + '---', + 'type: Note', + '_display: sheet', + '---', + '班级,平均分', + '一班,86', + '二班,91', + '三班,79', + '四班,88', +].join('\n') + +type EducationReceipt = { + capabilityId: string + id: string +} + +function readWorkspaceValue(key: string, fallback: string): string { + try { + return localStorage.getItem(key) ?? fallback + } catch { + return fallback + } +} + +function storeWorkspaceValue(key: string, value: string): void { + try { + localStorage.setItem(key, value) + } catch { + // The workspace remains editable in memory when browser storage is unavailable. + } +} + +function ComparisonChart({ metrics }: { metrics: ReturnType }) { + const maximum = Math.max(1, ...metrics.map((metric) => metric.value)) + return ( +
+ {metrics.map((metric) => ( +
+ {metric.label} +
+ {metric.value} +
+ ))} +
+ ) +} + +function Dashboard({ metrics }: { metrics: ReturnType }) { + const total = metrics.reduce((sum, metric) => sum + metric.value, 0) + const average = metrics.length > 0 ? Math.round(total / metrics.length * 10) / 10 : 0 + const highest = metrics.reduce((current, metric) => + metric.value > current.value ? metric : current, { label: '—', value: 0 }) + return ( +
+
班级数量{metrics.length}已进入对比范围
+
整体平均分{average}由当前表格计算
+
最高班级{highest.label}{highest.value} 分
+
+
+ ) +} + +function KnowledgeSurface() { + return ( +
+
EDU-KNOW-001课程标准与教案

可登记课程文档、教学目标和课堂活动。

+
EDU-KNOW-002学生学情数据

只在当前应用权限内参与整理、比较和投影。

+
EDU-KNOW-003教研协作资料

后续接入远端多人协作与版本回执。

+
+ ) +} + +export function EducationWorkLake({ locale }: { locale: AppLocale }) { + const t = (key: TranslationKey) => translate(locale, key) + const [instruction, setInstruction] = useState('') + const [surface, setSurface] = useState('knowledge') + const [receipt, setReceipt] = useState(null) + const [document, setDocument] = useState(() => readWorkspaceValue(DOCUMENT_STORAGE_KEY, DEFAULT_DOCUMENT)) + const [sheet, setSheet] = useState(() => readWorkspaceValue(SHEET_STORAGE_KEY, DEFAULT_SHEET)) + const metrics = useMemo(() => educationMetricsFromSheet(sheet), [sheet]) + + const runInstruction = () => { + const route = routeEducationInstruction(instruction) + const receiptId = `EDU-LOCAL-${Date.now().toString(36).toUpperCase()}` + setSurface(route.surface) + setReceipt({ capabilityId: route.capabilityId, id: receiptId }) + trackEvent('education_work_lake_instruction_routed', { + capabilityId: route.capabilityId, + reason: route.reason, + surface: route.surface, + }) + } + + const updateDocument = (value: string) => { + setDocument(value) + storeWorkspaceValue(DOCUMENT_STORAGE_KEY, value) + } + + const updateSheet = (_path: string, value: string) => { + setSheet(value) + storeWorkspaceValue(SHEET_STORAGE_KEY, value) + } + + return ( +
+
+
+

EDU-WORK-LAKE-001 · REMOTE FIRST

+

{t('education.title')}

+ {t('education.description')} +
+ +
+ +
+ setInstruction(event.target.value)} + placeholder={t('education.instructionPlaceholder')} + value={instruction} + /> + +
+ + {receipt && ( +
+ {t('education.openedInsideApp')} + {receipt.capabilityId} + {receipt.id} +
+ )} + + setSurface(value as EducationSurface)}> + + {t('education.document')} + {t('education.sheet')} + {t('education.chart')} + {t('education.dashboard')} + {t('education.knowledge')} + + +