From 29d557b8f925ee14268d4b84bb1b2c959d8de64c Mon Sep 17 00:00:00 2001 From: DTALEX66 <151195992+DTALEX66@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:25:07 +0800 Subject: [PATCH] feat(workspace): migrate ArcheAxis desktop A1 --- README.md | 11 ++- app/workspace/ui/assets/app.js | 34 +++++-- app/workspace/ui/assets/styles.css | 18 ++++ app/workspace/ui/index.html | 31 +++++- desktop/src-tauri/src/runtime.rs | 12 ++- docs/PRODUCT_POSITIONING.md | 61 ++++++++++++ docs/README.md | 1 + scripts/a0_browser_smoke.py | 151 ++++++++++++++++++++++++++++- 8 files changed, 295 insertions(+), 24 deletions(-) create mode 100644 docs/PRODUCT_POSITIONING.md diff --git a/README.md b/README.md index b4e1238..6543029 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,17 @@ > **本地优先的 Human + Agent + Cognitive Governance Desktop:同一份知识,人学得更深,AI 用得更准。** +ArcheAxis OS 以受治理的统一知识底座连接个人学习链和 AI 使用链。所有外部材料、人工输入和 AI 输出都先是可追溯的 Candidate;只有经过明确治理的内容才能进入后续学习或 Runtime 使用。 + ## 当前重点 -- **真实 Workspace 基础链**:网页、GitHub URL、本地文件导入 → candidate Research/Evidence → Knowledge/Learning/Mastery 治理;执行侧当前以 `read file:` 受限 Planner tracer 和局部闭环为主。 -- **可治理的运行时**:SQLite 持久化、Outbox/Receipt、失败不改状态、重试与回读,以及不暴露内部审计 ID 的公开投影。 -- **双端验证**:CI 已包含 Windows Tauri 构建、运行时和 NSIS 生命周期门禁;本地真实 HTTP → SQLite → Chromium upload → dispatch → receipt → reload smoke 已通过,Tauri WebView 点击级证据和公开发布资产仍在收口。 +- **资料到知识的真实基础链**:网页、GitHub URL、本地文件导入 → candidate Research/Evidence → Knowledge/Learning/Mastery 治理;执行侧当前以 `read file:` 受限 Planner tracer 和局部闭环为主。 +- **个人学习与 AI 使用的双向反馈**:学习笔记、纠错、练习和人工审核不会自动提升为事实;AI 的来源、Claim、解释、任务结果和 Lesson 同样必须先经 Candidate 治理。 +- **可治理的本地运行时**:SQLite 持久化、Outbox/Receipt、失败不改状态、重试与回读,以及不暴露内部审计 ID 的公开投影。 +- **桌面 Workspace A1**:默认 Apple-light,Violet Core 保留为暗色主题;一级 Rail、动态二级导航、上下文与证据检查器和真实活动坞均已接入。真实 Chromium 验证壳层交互后方可发布;Tauri WebView 点击级证据和公开发布资产仍未闭环,不能用 Chromium 结果替代。 - **当前版本**:`0.4.0`;Release Manifest 仍为 `unreleased / public=false`,公开 Alpha/Beta/Stable 发布尚未宣告。 -Research candidate 仍必须经过人工审查和来源独立性验证,不能自动当作 verified truth。当前事实、限制和验证证据见 [`docs/PROJECT_STATUS.md`](docs/PROJECT_STATUS.md) 与 [`docs/VERIFICATION_POLICY.md`](docs/VERIFICATION_POLICY.md)。 +Research candidate 仍必须经过人工审查和来源独立性验证,不能自动当作 verified truth。产品定位见 [`docs/PRODUCT_POSITIONING.md`](docs/PRODUCT_POSITIONING.md);当前事实、限制和验证证据见 [`docs/PROJECT_STATUS.md`](docs/PROJECT_STATUS.md) 与 [`docs/VERIFICATION_POLICY.md`](docs/VERIFICATION_POLICY.md)。 ## Phase 4 Research Status diff --git a/app/workspace/ui/assets/app.js b/app/workspace/ui/assets/app.js index b0a204d..82f75f0 100644 --- a/app/workspace/ui/assets/app.js +++ b/app/workspace/ui/assets/app.js @@ -1,10 +1,15 @@ const $=selector=>document.querySelector(selector),$$=selector=>[...document.querySelectorAll(selector)]; -const state={theme:localStorage.getItem('aa-theme')||'yaojin',page:'overview',intakeTrigger:null}; +const storedInspectorState=localStorage.getItem('aa-inspector'); +const state={theme:localStorage.getItem('aa-theme')||'apple-light',page:'overview',module:localStorage.getItem('aa-module')||'guanxin',intakeTrigger:null,subnavOpen:false,subnavTrigger:null,inspectorTrigger:null,inspectorCollapsed:storedInspectorState==='collapsed'||(window.matchMedia('(max-width: 960px)').matches&&storedInspectorState!=='open'),dockCollapsed:localStorage.getItem('aa-dock')==='collapsed'}; const icons={home:'M3 11l9-8 9 8v10a1 1 0 0 1-1 1h-5v-7h-6v7H4a1 1 0 0 1-1-1z',folder:'M3 6h6l2 2h10v12H3z',search:'M10 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm11 3-5-5',shield:'M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z',book:'M4 4h6a4 4 0 0 1 4 4v12a3 3 0 0 0-3-3H4zM20 4h-6',brain:'M8 4a4 4 0 0 0-4 4v1a4 4 0 0 0 0 8v1a4 4 0 0 0 4 4h1V4zM16 4a4 4 0 0 1 4 4v1a4 4 0 0 1 0 8v1a4 4 0 0 1-4 4h-1V4z',eye:'M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7zm10 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',layers:'M12 2 2 7l10 5 10-5zM2 17l10 5 10-5M2 12l10 5 10-5',cpu:'M6 6h12v12H6zM9 1v5M15 1v5M9 18v5M15 18v5M1 9h5M1 15h5M18 9h5M18 15h5',zap:'M13 2 3 14h8l-1 8 10-12h-8z',clock:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18zm0-13v5l3 2',tool:'M14 7a4 4 0 0 1 5-5l-3 3 2 2 3-3a4 4 0 0 1-5 5L7 18a2 2 0 0 1-3-3z',settings:'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm8-3a8 8 0 0 0-.2-1.8l2-1.6-2-3.4-2.4 1a8 8 0 0 0-3-1.8L14 2h-4l-.4 2.4a8 8 0 0 0-3 1.8l-2.4-1-2 3.4 2 1.6A8 8 0 0 0 4 12c0 .6.1 1.2.2 1.8l-2 1.6 2 3.4 2.4-1a8 8 0 0 0 3 1.8L10 22h4l.4-2.4a8 8 0 0 0 3-1.8l2 1.6c.1-.6.2-1.2.2-1.8z',activity:'M3 12h4l3 8 4-16 3 8h4',archive:'M3 4h18v5H3zm2 5v11h14V9M10 13h4',map:'M9 4 3 6v14l6-2 6 2 6-2V4l-6 2zm0 0v14m6-12v14',play:'M5 3l16 9-16 9z',list:'M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01'}; function iconElement(name){const namespace='http://www.w3.org/2000/svg';const svg=document.createElementNS(namespace,'svg');svg.setAttribute('viewBox','0 0 24 24');svg.setAttribute('aria-hidden','true');const path=document.createElementNS(namespace,'path');path.setAttribute('d',icons[name]||icons.folder);svg.append(path);return svg} -const nav=[['Core',[['overview','观心总览','Overview','home'],['projects','项目','Projects','folder'],['research','察微研究','Research','search'],['evidence','证据中心','Evidence','shield'],['knowledge','藏识知识','Knowledge','archive'],['machine','知衡机器知识','Machine','cpu'],['runtime','知行任务执行','Runtime','zap'],['evolution','知新评估进化','Evolution','activity']]],['Learning OS',[['courses','课程中心','Courses','book'],['learning','学习路线','Learning','map'],['review','复习','Review','clock'],['training','训练','Training','play'],['visual','显象可视化教学','Visual','layers'],['palace','知境空间宫殿','Palace','map'],['skills','技能实战','Skills','tool'],['teachback','输出教学','Teach Back','eye'],['consolidation','长期巩固','Consolidation','archive'],['report','学习报告','Report','list']]],['System',[['models','模型与工具','Models','cpu'],['agents','Agent管理','Agents','brain'],['audit','审计记录','Audit','shield'],['diagnostics','系统诊断','Diagnostics','activity'],['settings','设置','Settings','settings']]]]; -function renderNav(){const target=$('#nav');target.textContent='';nav.forEach(([group,items])=>{const section=document.createElement('section');section.className='nav-group';const title=document.createElement('div');title.className='nav-title';const groupName=document.createElement('span');groupName.textContent=group;const separator=document.createElement('span');separator.textContent='—';title.append(groupName,separator);section.append(title);items.forEach(item=>{const button=document.createElement('button');const active=state.page===item[0];button.type='button';button.className=`nav-item ${active?'active':''}`;button.dataset.page=item[0];button.title=item[1];if(active)button.setAttribute('aria-current','page');const chinese=document.createElement('span');chinese.className='cn';chinese.textContent=item[1];const english=document.createElement('span');english.className='en';english.textContent=item[2];button.append(iconElement(item[3]),chinese,english);section.append(button)});target.append(section)})} -function setTheme(theme){const selected=['yaojin','deepspace'].includes(theme)?theme:'yaojin';state.theme=selected;localStorage.setItem('aa-theme',selected);document.body.dataset.theme=selected;$$('.theme button').forEach(button=>{const active=button.dataset.theme===selected;button.classList.toggle('active',active);button.setAttribute('aria-pressed',String(active));button.type='button'})} +const nav=[['观心',[['overview','观心总览','Overview','home'],['projects','项目','Projects','folder']]],['知行',[['runtime','知行任务执行','Runtime','zap'],['delivery','投递回执','Delivery','archive']]],['察微',[['research','察微研究','Research','search'],['evidence','证据中心','Evidence','shield']]],['藏识',[['knowledge','藏识知识','Knowledge','archive'],['learning','学习路线','Learning','map'],['evolution','知新评估进化','Evolution','activity'],['machine','知衡机器知识','Machine','cpu']]],['智体',[['agents','Agent管理','Agents','brain'],['skills','技能实战','Skills','tool'],['models','模型与工具','Models','cpu']]],['流程',[['workflow-builder','流程构建器','Builder','layers'],['workflow-runs','流程运行记录','Runs','clock']]],['连接',[['integrations','外部连接','Integrations','folder'],['mcp','MCP工具','MCP','tool']]],['系统',[['diagnostics','系统诊断','Diagnostics','activity'],['audit','审计记录','Audit','shield'],['settings','设置','Settings','settings']]]]; +const routeStatus={overview:'available',projects:'planned',runtime:'partial',delivery:'partial',research:'available',evidence:'available',knowledge:'available',learning:'partial',evolution:'partial',machine:'partial',agents:'planned',skills:'planned',models:'planned','workflow-builder':'planned','workflow-runs':'planned',integrations:'planned',mcp:'planned',diagnostics:'available',audit:'planned',settings:'planned'}; +const routeStatusLabels={available:'已接入',partial:'部分接入',planned:'规划中',blocked:'已阻断'}; +const pageModule={overview:'guanxin',projects:'guanxin',runtime:'zhixing',delivery:'zhixing',research:'chawei',evidence:'chawei',knowledge:'cangshi',learning:'cangshi',evolution:'cangshi',machine:'cangshi',agents:'zhiti',skills:'zhiti',models:'zhiti','workflow-builder':'workflow','workflow-runs':'workflow',integrations:'connect',mcp:'connect',diagnostics:'system',audit:'system',settings:'system'}; +const groupModule={'观心':'guanxin','知行':'zhixing','察微':'chawei','藏识':'cangshi','智体':'zhiti','流程':'workflow','连接':'connect','系统':'system'}; +function renderNav(){const target=$('#nav');target.textContent='';state.module=pageModule[state.page]||state.module;localStorage.setItem('aa-module',state.module);$$('.rail-item').forEach(button=>{const active=button.dataset.module===state.module;button.classList.toggle('active',active);button.setAttribute('aria-current',active?'page':'false')});nav.forEach(([group,items])=>{const section=document.createElement('section');section.className='nav-group';section.dataset.module=groupModule[group];section.classList.toggle('active-group',section.dataset.module===state.module);const title=document.createElement('div');title.className='nav-title';const groupName=document.createElement('span');groupName.textContent=group;const separator=document.createElement('span');separator.textContent='—';title.append(groupName,separator);section.append(title);items.forEach(item=>{const button=document.createElement('button');const active=state.page===item[0];button.type='button';button.className=`nav-item ${active?'active':''}`;button.dataset.page=item[0];button.title=item[1];if(active)button.setAttribute('aria-current','page');const chinese=document.createElement('span');chinese.className='cn';chinese.textContent=item[1];const english=document.createElement('span');english.className='en';english.textContent=item[2];const stateBadge=document.createElement('span');const status=routeStatus[item[0]]||'planned';stateBadge.className=`route-state route-state-${status}`;stateBadge.textContent=routeStatusLabels[status];stateBadge.title=`状态:${routeStatusLabels[status]}`;button.dataset.routeState=status;button.append(iconElement(item[3]),chinese,english,stateBadge);section.append(button)});target.append(section)})} +function setTheme(theme){const selected=['violet-core','apple-light','yaojin','deepspace'].includes(theme)?theme:'apple-light';state.theme=selected;localStorage.setItem('aa-theme',selected);document.body.dataset.theme=selected;$$('.theme button').forEach(button=>{const active=button.dataset.theme===selected;button.classList.toggle('active',active);button.setAttribute('aria-pressed',String(active));button.type='button'})} function metric(label,value,detail,tone='info'){const card=document.createElement('div');card.className='card metric';const name=document.createElement('span');name.textContent=label;const number=document.createElement('b');number.textContent=String(value);const note=document.createElement('span');note.className=`delta ${tone}`;note.textContent=detail;card.append(name,number,note);return card} const capabilityLabels={asynchronous_worker:'异步Worker',outbox_dispatcher:'Outbox投递器',server_sent_events:'实时事件流',interactive_job_center:'交互式Job Center',asr_transcription:'ASR转写'}; const capabilityStates=new Set(['available','dependency_required','not_implemented']); @@ -43,18 +48,31 @@ stageRow('#lifecycle-execution',stages.execution);stageRow('#lifecycle-trace',st function renderLifecycleUnavailable(){const permission=$('#lifecycle-permission');permission.textContent='';permission.append(metric('权限门禁','不可用','本地生命周期读取失败','bad'));for(const id of['#lifecycle-execution','#lifecycle-trace','#lifecycle-evaluation','#lifecycle-lesson']){const target=$(id);target.textContent='';const row=document.createElement('div');row.className='row';const main=document.createElement('div');main.className='row-main';const name=document.createElement('b');name.textContent='生命周期不可用';const detail=document.createElement('span');detail.textContent='本地生命周期读取失败';main.append(name,detail);row.append(main);target.append(row)}} async function refreshLifecycle(){try{renderLifecycle(validateLifecycle(await fetchJson('/workspace/api/lifecycle')))}catch{renderLifecycleUnavailable()}} function labelFor(page){for(const [,items] of nav){const item=items.find(candidate=>candidate[0]===page);if(item)return item[1]}return '功能尚未接入'} -function openPage(requestedPage){const page=productRoutes.has(requestedPage)?requestedPage:'unavailable';const requested=document.getElementById(`page-${page}`);const visible=requested||document.getElementById('page-unavailable');state.page=page;renderNav();$$('.page').forEach(section=>section.classList.toggle('active',section===visible));if(visible.id==='page-unavailable')$('#unavailable-title').textContent=labelFor(page);history.replaceState(null,'',`#${page}`);if(page==='overview')void refreshStatus();if(page==='diagnostics')void refreshDiagnostics();if(page==='runtime')void refreshRuntime();if(page==='research')void refreshResearch();if(page==='knowledge')void refreshKnowledge();if(page==='learning')void refreshLearning();if(page==='evolution')void refreshEvolution();if(page==='machine')void refreshMachine();if(page==='evidence')void refreshLifecycle()} +const mobileSubnavQuery=window.matchMedia('(max-width: 960px)'); +function activeModuleTrigger(){return $(`.rail-item[data-module="${state.module}"]`)} +function syncSubnavAccessibility(){const mobile=mobileSubnavQuery.matches;const open=mobile&&state.subnavOpen;const target=$('#nav');if(target){target.toggleAttribute('inert',mobile&&!open);target.setAttribute('aria-hidden',String(mobile&&!open))}$$('.rail-item[data-module]').forEach(button=>button.setAttribute('aria-expanded',String(mobile?open&&button===state.subnavTrigger:button===activeModuleTrigger())))} +function closeSubnav({restoreFocus=false}={}){const wasOpen=state.subnavOpen;const trigger=state.subnavTrigger;state.subnavOpen=false;document.body.classList.remove('subnav-open');syncSubnavAccessibility();if(restoreFocus&&wasOpen&&trigger?.isConnected)trigger.focus()} +function handleSubnavBreakpointChange(event){const target=$('#nav');state.subnavOpen=false;document.body.classList.remove('subnav-open');if(event.matches&&target?.contains(document.activeElement))activeModuleTrigger()?.focus();applyShellState();syncSubnavAccessibility()} +mobileSubnavQuery.addEventListener('change',handleSubnavBreakpointChange); +function openPage(requestedPage){const page=productRoutes.has(requestedPage)?requestedPage:'unavailable';const requested=document.getElementById(`page-${page}`);const visible=requested||document.getElementById('page-unavailable');state.page=page;closeSubnav();renderNav();$$('.page').forEach(section=>section.classList.toggle('active',section===visible));if(visible.id==='page-unavailable')$('#unavailable-title').textContent=labelFor(page);history.replaceState(null,'',`#${page}`);if(page==='overview')void refreshStatus();if(page==='diagnostics')void refreshDiagnostics();if(page==='runtime')void refreshRuntime();if(page==='research')void refreshResearch();if(page==='knowledge')void refreshKnowledge();if(page==='learning')void refreshLearning();if(page==='evolution')void refreshEvolution();if(page==='machine')void refreshMachine();if(page==='evidence')void refreshLifecycle()} function openIntake(trigger){const modal=$('#intake-modal'),result=$('#intake-result');state.intakeTrigger=trigger;result.textContent='';result.style.display='none';modal.setAttribute('aria-hidden','false');modal.classList.add('open');$('#intake-url').focus()} function closeIntake(){const modal=$('#intake-modal');if(!modal.classList.contains('open'))return;modal.classList.remove('open');modal.setAttribute('aria-hidden','true');const trigger=state.intakeTrigger;state.intakeTrigger=null;if(trigger?.isConnected)trigger.focus()} function trapIntakeFocus(event){if(event.key!=='Tab'||!$('#intake-modal').classList.contains('open'))return;const focusable=$$('#intake-modal button:not([disabled]),#intake-modal input:not([disabled])');if(!focusable.length)return;const first=focusable[0],last=focusable.at(-1);if(event.shiftKey&&document.activeElement===first){event.preventDefault();last.focus()}else if(!event.shiftKey&&document.activeElement===last){event.preventDefault();first.focus()}} function validateIntakeResult(payload){const sourceTypes=new Set(['file','web','github_repository']);if(!isRecord(payload)||!sourceTypes.has(payload.source_type)||typeof payload.requires_human_review!=='boolean')throw new Error('invalid intake result');if(payload.source_type==='file'){if(!['file_name','format','engine'].every(key=>typeof payload[key]==='string'&&payload[key].trim())||!Number.isInteger(payload.char_count)||payload.char_count<0||(payload.content_preview!==undefined&&typeof payload.content_preview!=='string'))throw new Error('invalid file intake result')}else if(!['source_count','claim_count','evidence_count'].every(key=>Number.isInteger(payload[key])&&payload[key]>=0))throw new Error('invalid source intake result');return payload} function intakeFailureDetail(payload){return isRecord(payload)&&typeof payload.detail==='string'&&payload.detail.trim()?payload.detail:'请检查资料后重试'} -async function showIntakeResult(response){const result=$('#intake-result');let payload;try{payload=await response.json()}catch{result.textContent=`处理失败\n${response.ok?'本地服务返回无效结果':'无法读取处理结果'}`;return}if(!response.ok){result.textContent=`处理失败\n${intakeFailureDetail(payload)}`;return}try{payload=validateIntakeResult(payload)}catch{result.textContent='处理失败\n本地服务返回无效结果';return}const lines=['处理完成'];const sourceNames={file:'本地文件',web:'网页',github_repository:'GitHub仓库'};lines.push(`来源:${sourceNames[payload.source_type]}`);if(payload.format)lines.push(`格式:${payload.format}`);if(payload.engine)lines.push(`引擎:${payload.engine}`);if(Number.isInteger(payload.char_count))lines.push(`内容长度:${payload.char_count} 字符`);if(Number.isInteger(payload.source_count))lines.push(`来源记录:${payload.source_count}`);if(Number.isInteger(payload.claim_count))lines.push(`候选要点:${payload.claim_count}`);if(Number.isInteger(payload.evidence_count))lines.push(`证据记录:${payload.evidence_count}`);if(payload.requires_human_review)lines.push('下一步:等待人工复核');if(typeof payload.content_preview==='string'&&payload.content_preview.trim())lines.push(`内容预览:${payload.content_preview.trim()}`);result.textContent=lines.join('\n');void refreshStatus()} +async function showIntakeResult(response){const result=$('#intake-result');let payload;try{payload=await response.json()}catch{result.textContent=`处理失败\n${response.ok?'本地服务返回无效结果':'无法读取处理结果'}`;return}if(!response.ok){result.textContent=`处理失败\n${intakeFailureDetail(payload)}`;return}try{payload=validateIntakeResult(payload)}catch{result.textContent='处理失败\n本地服务返回无效结果';return}const lines=['处理完成'];const sourceNames={file:'本地文件',web:'网页',github_repository:'GitHub仓库'};lines.push(`来源:${sourceNames[payload.source_type]}`);if(payload.format)lines.push(`格式:${payload.format}`);if(payload.engine)lines.push(`引擎:${payload.engine}`);if(Number.isInteger(payload.char_count))lines.push(`内容长度:${payload.char_count} 字符`);if(Number.isInteger(payload.source_count))lines.push(`来源记录:${payload.source_count}`);if(Number.isInteger(payload.claim_count))lines.push(`候选要点:${payload.claim_count}`);if(Number.isInteger(payload.evidence_count))lines.push(`证据记录:${payload.evidence_count}`);if(payload.requires_human_review)lines.push('下一步:等待人工复核');if(typeof payload.content_preview==='string'&&payload.content_preview.trim())lines.push(`内容预览:${payload.content_preview.trim()}`);result.textContent=lines.join('\n');await Promise.all([refreshStatus(),refreshActivityDock()]);if(state.page==='runtime')await refreshRuntime()} async function runIntake(requestFactory){const result=$('#intake-result'),buttons=$$('#intake-modal button[type="submit"]');if(buttons.some(button=>button.disabled))return;result.style.display='block';result.textContent='处理中…';buttons.forEach(button=>{button.disabled=true});try{await showIntakeResult(await requestFactory())}catch{result.textContent='处理失败\n无法连接本地服务,请重试'}finally{buttons.forEach(button=>{button.disabled=false})}} -document.addEventListener('click',event=>{const theme=event.target.closest('button[data-theme]');if(theme){setTheme(theme.dataset.theme);return}const page=event.target.closest('[data-page]');if(page){const restoreNavFocus=page.classList.contains('nav-item');openPage(page.dataset.page);if(restoreNavFocus)$(`.nav-item[data-page="${page.dataset.page}"]`)?.focus();return}const action=event.target.closest('[data-action]');if(!action)return;if(action.dataset.action==='intake')openIntake(action);if(action.dataset.action==='intake-close')closeIntake();if(action.dataset.action==='refresh'){void refreshDiagnostics();void refreshStatus()}if(action.dataset.action==='jobs-refresh')void refreshRuntime();if(action.dataset.action==='research-refresh')void refreshResearch();if(action.dataset.action==='knowledge-refresh')void refreshKnowledge();if(action.dataset.action==='learning-refresh')void refreshLearning();if(action.dataset.action==='evolution-refresh')void refreshEvolution();if(action.dataset.action==='machine-refresh')void refreshMachine();if(action.dataset.action==='lifecycle-refresh')void refreshLifecycle();if(action.dataset.action==='delivery-dispatch'){fetch('/workspace/api/delivery/dispatch',{method:'POST'}).then(response=>{if(!response.ok)throw new Error('delivery failed');return refreshRuntime()}).catch(()=>{$('#delivery-center').textContent='投递失败;请刷新状态后重试'})}if(action.dataset.action==='delivery-retry'){fetch('/workspace/api/delivery/retry',{method:'POST'}).then(response=>{if(!response.ok)throw new Error('retry failed');return refreshRuntime()}).catch(()=>{$('#delivery-center').textContent='重试失败;请刷新状态后重试'})}if(action.dataset.action==='research-approve'){fetch('/workspace/api/research/approve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('research-review'),source:action.dataset.source})}).then(response=>{if(!response.ok)throw new Error('approval failed');return Promise.all([refreshResearch(),refreshKnowledge()])}).catch(()=>{$('#research-queue').textContent='批准失败;请刷新队列后重试'})}if(action.dataset.action==='knowledge-start-learning'){fetch('/workspace/api/knowledge/start-learning',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('learning-start'),source:action.dataset.source})}).then(response=>{if(!response.ok)throw new Error('learning start failed');return Promise.all([refreshKnowledge(),refreshLearning()])}).catch(()=>{$('#knowledge-queue').textContent='开始学习失败;请刷新后重试'})}if(action.dataset.action==='learning-practice'){fetch('/workspace/api/learning/practice',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('learning-practice'),source:action.dataset.source,quality:5})}).then(response=>{if(!response.ok)throw new Error('practice failed');return Promise.all([refreshLearning(),refreshEvolution(),refreshMachine()])}).catch(()=>{$('#learning-queue').textContent='记录练习失败;请刷新后重试'})}if(action.dataset.action==='runtime-approve'){fetch('/workspace/api/runtime/approve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('runtime-approve'),title:action.dataset.title})}).then(response=>{if(!response.ok)throw new Error('runtime approval failed');return Promise.all([refreshMachine(),refreshEvolution()])}).catch(()=>{$('#machine-queue').textContent='Runtime 治理批准失败;请刷新后重试'})}}); -document.addEventListener('keydown',event=>{trapIntakeFocus(event);if(event.key==='Escape')closeIntake()}); +document.addEventListener('click',event=>{const theme=event.target.closest('button[data-theme]');if(theme){setTheme(theme.dataset.theme);return}const page=event.target.closest('[data-page]');if(page){const restoreNavFocus=page.classList.contains('nav-item');const mobileTrigger=restoreNavFocus&&window.matchMedia('(max-width: 960px)').matches?state.subnavTrigger:null;openPage(page.dataset.page);if(mobileTrigger?.isConnected)mobileTrigger.focus();else if(restoreNavFocus)$(`.nav-item[data-page="${page.dataset.page}"]`)?.focus();return}const action=event.target.closest('[data-action]');if(!action)return;if(action.dataset.action==='intake')openIntake(action);if(action.dataset.action==='intake-close')closeIntake();if(action.dataset.action==='refresh'){void refreshDiagnostics();void refreshStatus()}if(action.dataset.action==='jobs-refresh')void refreshRuntime();if(action.dataset.action==='research-refresh')void refreshResearch();if(action.dataset.action==='knowledge-refresh')void refreshKnowledge();if(action.dataset.action==='learning-refresh')void refreshLearning();if(action.dataset.action==='evolution-refresh')void refreshEvolution();if(action.dataset.action==='machine-refresh')void refreshMachine();if(action.dataset.action==='lifecycle-refresh')void refreshLifecycle();if(action.dataset.action==='delivery-dispatch'){fetch('/workspace/api/delivery/dispatch',{method:'POST'}).then(response=>{if(!response.ok)throw new Error('delivery failed');return refreshRuntime()}).catch(()=>{$('#delivery-center').textContent='投递失败;请刷新状态后重试'})}if(action.dataset.action==='delivery-retry'){fetch('/workspace/api/delivery/retry',{method:'POST'}).then(response=>{if(!response.ok)throw new Error('retry failed');return refreshRuntime()}).catch(()=>{$('#delivery-center').textContent='重试失败;请刷新状态后重试'})}if(action.dataset.action==='research-approve'){fetch('/workspace/api/research/approve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('research-review'),source:action.dataset.source})}).then(response=>{if(!response.ok)throw new Error('approval failed');return Promise.all([refreshResearch(),refreshKnowledge()])}).catch(()=>{$('#research-queue').textContent='批准失败;请刷新队列后重试'})}if(action.dataset.action==='knowledge-start-learning'){fetch('/workspace/api/knowledge/start-learning',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('learning-start'),source:action.dataset.source})}).then(response=>{if(!response.ok)throw new Error('learning start failed');return Promise.all([refreshKnowledge(),refreshLearning()])}).catch(()=>{$('#knowledge-queue').textContent='开始学习失败;请刷新后重试'})}if(action.dataset.action==='learning-practice'){fetch('/workspace/api/learning/practice',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('learning-practice'),source:action.dataset.source,quality:5})}).then(response=>{if(!response.ok)throw new Error('practice failed');return Promise.all([refreshLearning(),refreshEvolution(),refreshMachine()])}).catch(()=>{$('#learning-queue').textContent='记录练习失败;请刷新后重试'})}if(action.dataset.action==='runtime-approve'){fetch('/workspace/api/runtime/approve',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command_id:commandId('runtime-approve'),title:action.dataset.title})}).then(response=>{if(!response.ok)throw new Error('runtime approval failed');return Promise.all([refreshMachine(),refreshEvolution()])}).catch(()=>{$('#machine-queue').textContent='Runtime 治理批准失败;请刷新后重试'})}}); +document.addEventListener('keydown',event=>{trapIntakeFocus(event);if(event.key==='Escape'){closeIntake();closeSubnav({restoreFocus:true})}}); $('#intake-modal').addEventListener('click',event=>{if(event.target.id==='intake-modal')closeIntake()}); $('#intake-url-form').addEventListener('submit',async event=>{event.preventDefault();const url=new FormData(event.currentTarget).get('url');await runIntake(()=>fetch('/workspace/api/intake/url',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})}))}); $('#intake-file-form').addEventListener('submit',async event=>{event.preventDefault();await runIntake(()=>fetch('/workspace/api/intake/upload',{method:'POST',body:new FormData(event.currentTarget)}))}); window.addEventListener('hashchange',()=>openPage(location.hash.slice(1)||'overview')); setTheme(state.theme);openPage(location.hash.slice(1)||'overview');setInterval(()=>{$('#clock').textContent=new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',second:'2-digit'})},1000);setInterval(()=>{if(state.page==='runtime')void refreshRuntime();if(state.page==='evidence')void refreshLifecycle()},3000); + +function showShellToast(message){const toast=$('#toast');if(!toast)return;toast.textContent=message;toast.classList.add('show');window.clearTimeout(showShellToast.timer);showShellToast.timer=window.setTimeout(()=>toast.classList.remove('show'),2600)} +function applyShellState({restoreFocus=false}={}){document.body.classList.toggle('inspector-collapsed',state.inspectorCollapsed);document.body.classList.toggle('dock-collapsed',state.dockCollapsed);const inspector=$('.inspector');const mobile=mobileSubnavQuery.matches;const open=!state.inspectorCollapsed;if(inspector){inspector.classList.toggle('open',open);inspector.toggleAttribute('inert',mobile&&!open);inspector.setAttribute('aria-hidden',String(mobile&&!open))}$$('[data-action="inspector-toggle"]').forEach(button=>{button.setAttribute('aria-expanded',String(open));button.setAttribute('aria-label',open?'折叠上下文与证据检查器':'展开上下文与证据检查器')});const internal=$('.inspector-internal-trigger');if(internal)internal.textContent='×';const external=$('.inspector-external-trigger');if(external)external.textContent='◧';const dockButton=$('[data-action="dock-toggle"]');if(dockButton)dockButton.textContent=state.dockCollapsed?'⌃':'⌄';if(dockButton)dockButton.setAttribute('aria-label',state.dockCollapsed?'展开活动坞':'折叠活动坞');if(restoreFocus&&state.inspectorTrigger?.isConnected)state.inspectorTrigger.focus()} +function setModule(module,trigger){const group=nav.find(([name])=>groupModule[name]===module);if(!group)return;state.module=module;state.subnavTrigger=trigger||null;localStorage.setItem('aa-module',module);const firstPage=group[1][0][0];openPage(firstPage);$('.subnav')?.scrollTo({top:0,behavior:'smooth'});if(window.matchMedia('(max-width: 960px)').matches){state.subnavOpen=true;document.body.classList.add('subnav-open');syncSubnavAccessibility();const firstRoute=$(`.nav-group[data-module="${module}"] .nav-item`);firstRoute?.focus()}else syncSubnavAccessibility()} +async function refreshActivityDock(){try{const [jobs,delivery,research]=await Promise.all([fetchJson('/workspace/api/jobs').then(validateJobs),fetchJson('/workspace/api/delivery').then(validateDelivery),fetchJson('/workspace/api/research')]);if(!isRecord(research)||research.schema_version!=='v1'||!Array.isArray(research.items))throw new Error('invalid workspace research');const reviewCount=research.items.length;const pending=(delivery.summary.outbox?.pending||0)+(delivery.summary.outbox?.failed||0);$('#dock-jobs').textContent=String(jobs.jobs.length);$('#dock-delivery').textContent=`${pending} / ${delivery.summary.receipts?.recorded||0}`;$('#dock-review').textContent=String(reviewCount);$('#dock-summary').textContent=`${jobs.jobs.length} jobs · ${pending} pending/failed · ${reviewCount} review`;$('#inspector-status').textContent='本地 API 可读';$('#inspector-evidence').textContent=`${delivery.summary.receipts?.recorded||0} receipts`}catch{$('#dock-summary').textContent='真实活动读取失败';$('#dock-jobs').textContent='不可用';$('#dock-delivery').textContent='不可用';$('#dock-review').textContent='不可用';$('#inspector-status').textContent='不可用';$('#inspector-evidence').textContent='等待重新读取'}} +document.addEventListener('click',event=>{const rail=event.target.closest('.rail-item[data-module]');if(rail){setModule(rail.dataset.module,rail);return}const action=event.target.closest('[data-action]');if(!action)return;if(action.dataset.action==='inspector-toggle'){const closing=!state.inspectorCollapsed;if(!closing)state.inspectorTrigger=action;state.inspectorCollapsed=closing;localStorage.setItem('aa-inspector',state.inspectorCollapsed?'collapsed':'open');applyShellState({restoreFocus:closing});if(!closing)$('.inspector-internal-trigger')?.focus();return}if(action.dataset.action==='dock-toggle'){state.dockCollapsed=!state.dockCollapsed;localStorage.setItem('aa-dock',state.dockCollapsed?'collapsed':'open');applyShellState();return}if(action.dataset.action==='command-open'){showShellToast('全局命令入口规划中;当前请使用左侧真实工作区入口');$('#global-search')?.focus();return}}); +applyShellState();syncSubnavAccessibility();void refreshActivityDock();setInterval(()=>void refreshActivityDock(),3000); diff --git a/app/workspace/ui/assets/styles.css b/app/workspace/ui/assets/styles.css index a455eca..098e2d7 100644 --- a/app/workspace/ui/assets/styles.css +++ b/app/workspace/ui/assets/styles.css @@ -10,3 +10,21 @@ body[data-theme='deepspace']{--bg:#0A0E1A;--deep:#060911;--panel:#121726;--panel .editor{display:grid;grid-template-columns:220px minmax(0,1fr) 320px;grid-template-rows:auto minmax(480px,1fr) 58px;gap:0;border:1px solid var(--line);border-radius:var(--r3);overflow:hidden;background:var(--panel2);height:680px}.editor-top{grid-column:1/-1;height:48px;display:flex;align-items:center;gap:8px;padding:0 12px;border-bottom:1px solid var(--line);background:var(--panel)}.editor-left{border-right:1px solid var(--line);overflow:auto}.editor-canvas{position:relative;background-image:radial-gradient(circle,var(--line) 1px,transparent 1px);background-size:22px 22px;overflow:auto}.editor-right{border-left:1px solid var(--line);overflow:auto}.editor-bottom{grid-column:1/-1;display:flex;align-items:center;gap:10px;border-top:1px solid var(--line);padding:0 12px;background:var(--panel)}.node{position:absolute;min-width:150px;border:1px solid var(--line2);border-radius:9px;background:var(--panel);box-shadow:var(--shadow);padding:10px}.node b{display:block}.node span{color:var(--muted);font-size:11px}.node.fact{border-color:var(--info)}.node.memory{border-color:var(--accent)}.node.teach{border-color:var(--ok)}.edge{position:absolute;height:1px;background:var(--line2);transform-origin:left center}.palace{position:relative;height:620px;background:radial-gradient(circle at 50% 30%,var(--panel3),var(--deep));border:1px solid var(--line);border-radius:var(--r3);overflow:hidden;perspective:900px}.room{position:absolute;border:1px solid var(--line2);background:linear-gradient(180deg,var(--panel),var(--panel2));border-radius:10px;padding:10px;transform:rotateX(55deg) rotateZ(-8deg);box-shadow:var(--shadow)}.room:after{content:'';position:absolute;inset:8px;border:1px dashed var(--line);border-radius:6px}.locus{position:absolute;width:16px;height:16px;border-radius:50%;background:var(--accent);border:3px solid var(--deep);box-shadow:0 0 0 2px var(--accent)}.route{position:absolute;height:2px;background:linear-gradient(90deg,transparent,var(--accent),transparent)} .runtime{display:grid;grid-template-columns:260px minmax(0,1fr) 340px;grid-template-rows:auto minmax(0,1fr) 190px;gap:var(--gap);height:720px}.runtime-head{grid-column:1/-1}.runtime-trace{grid-column:1/-1}.step{display:flex;gap:9px;padding:10px;border:1px solid var(--line);border-radius:8px;margin-bottom:8px;background:var(--panel2)}.step .n{width:22px;height:22px;border-radius:50%;background:var(--deep);border:1px solid var(--line2);display:grid;place-items:center;font:700 10px var(--mono);color:var(--accent)}.step.active{border-color:var(--accent);background:var(--soft)}.log{height:100%;overflow:auto;background:var(--deep);border:1px solid var(--line);border-radius:8px;padding:12px;font:12px/1.6 var(--mono)}.log .t{color:var(--muted)}.log .tool{color:var(--info)}.log .ok{color:var(--ok)}.log .warn{color:var(--warn)}.log .bad{color:var(--bad)}.permission{border:1px solid var(--warn);background:color-mix(in srgb,var(--warn) 9%,var(--panel));border-radius:8px;padding:12px}.job{position:fixed;left:var(--nav-w);right:0;bottom:0;height:var(--job-h);background:var(--glass);backdrop-filter:blur(14px);border-top:1px solid var(--line);z-index:25;display:grid;grid-template-columns:220px minmax(0,1fr) 220px}.job-title{padding:14px 16px;border-right:1px solid var(--line)}.job.collapsed{height:64px;grid-template-columns:minmax(0,1fr) 180px;overflow:hidden}.job.collapsed .job-list{display:none}.job.collapsed .job-title{border-right:0}.job.collapsed .job-actions{padding:10px;border-left:1px solid var(--line)}.job-list{padding:10px 16px;overflow:auto}.job-actions{border-left:1px solid var(--line);padding:14px}.job-row{display:grid;grid-template-columns:180px minmax(0,1fr) 150px 130px;align-items:center;gap:12px;padding:8px 0;border-bottom:1px solid var(--line)}.job-row:last-child{border-bottom:0}.drawer{position:fixed;top:var(--top-h);right:0;bottom:var(--job-h);width:420px;background:var(--glass);backdrop-filter:blur(18px);border-left:1px solid var(--line);z-index:35;transform:translateX(100%);transition:transform .18s ease;box-shadow:var(--shadow)}.drawer.open{transform:none}.drawer-head{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid var(--line)}.drawer-body{padding:16px;overflow:auto;height:calc(100% - 58px)}.kv{display:grid;grid-template-columns:110px 1fr;gap:9px 12px;margin-bottom:16px}.kv dt{color:var(--muted);font-size:12px}.kv dd{margin:0;min-width:0;overflow-wrap:anywhere}.modal{position:fixed;inset:0;background:rgba(0,0,0,.48);display:none;place-items:center;padding:16px;z-index:60}.modal.open{display:grid}.dialog{width:min(560px,100%);max-height:calc(100dvh - 32px);overflow:auto;background:var(--panel);border:1px solid var(--line2);border-radius:12px;box-shadow:var(--shadow);padding:18px}.palette{position:fixed;inset:0;background:rgba(0,0,0,.42);display:none;z-index:70}.palette.open{display:block}.palette-box{width:min(720px,92vw);margin:10vh auto 0;background:var(--glass);backdrop-filter:blur(20px);border:1px solid var(--line2);border-radius:12px;box-shadow:var(--shadow);overflow:hidden}.palette-box input{width:100%;height:50px;background:transparent;border:0;border-bottom:1px solid var(--line);padding:0 16px;outline:none;font-size:16px}.palette-item{display:flex;align-items:center;gap:10px;padding:12px 16px;border-bottom:1px solid var(--line)}.palette-item:hover{background:var(--soft)}.empty{border:1px dashed var(--line2);border-radius:10px;padding:26px;text-align:center;color:var(--muted)}.empty b{display:block;color:var(--text);font-size:16px;margin-bottom:6px}.toast{position:fixed;right:22px;bottom:calc(var(--job-h) + 20px);background:var(--panel);border:1px solid var(--line2);border-radius:9px;padding:11px 13px;box-shadow:var(--shadow);z-index:80;display:none}.toast.show{display:block}.mono{font-family:var(--mono)}.gold{color:var(--accent)}.muted{color:var(--muted)}.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}.info{color:var(--info)}.view-switch{display:flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}.view-switch button{border:0;background:var(--panel2);padding:6px 9px;color:var(--muted)}.view-switch button.active{background:var(--accent);color:var(--deep);font-weight:700}.filterbar{display:flex;gap:8px;align-items:center;margin-bottom:12px}.filterbar input,.filterbar select{height:34px;border:1px solid var(--line);border-radius:7px;background:var(--panel2);padding:0 9px}.form{display:grid;gap:12px}.field{display:grid;gap:6px}.field label{font-weight:700}.field input,.field textarea,.field select{border:1px solid var(--line);border-radius:7px;background:var(--deep);padding:9px 10px;min-width:0}.field textarea{min-height:100px;resize:vertical}.result{display:none;max-height:220px;margin:12px 0 0;padding:10px;border:1px solid var(--line);border-radius:7px;background:var(--deep);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere}.segmented{display:flex;gap:6px;flex-wrap:wrap}.segmented button{border:1px solid var(--line);background:var(--panel2);border-radius:999px;padding:6px 10px;color:var(--muted)}.segmented button.active{color:var(--accent);border-color:var(--accent);background:var(--soft)} @media(max-width:1440px){:root{--nav-w:220px}.grid.cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:240px minmax(0,1fr) 340px}.drawer{width:380px}.main{padding-left:20px;padding-right:20px}}@media(max-width:1280px){:root{--nav-w:72px;--top-h:56px;--job-h:150px}.brand-name,.brand-sub,.workspace-pill span,.project-pill,.user .meta,.nav-title,.nav-item .cn,.nav-item .en{display:none}.brand{width:54px;justify-content:center}.nav{padding-left:8px;padding-right:8px}.nav-item{justify-content:center;padding:9px}.grid.cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}.grid.cols-3{grid-template-columns:repeat(2,minmax(0,1fr))}.split{grid-template-columns:220px minmax(0,1fr)}.split .pane:last-child{grid-column:1/-1}.editor{grid-template-columns:190px minmax(0,1fr);grid-template-rows:auto minmax(420px,1fr) auto auto}.editor-right{grid-column:1/-1;border-left:0;border-top:1px solid var(--line);max-height:220px}.runtime{grid-template-columns:220px minmax(0,1fr);height:auto}.runtime>aside:last-of-type{grid-column:1/-1}.runtime-trace{min-height:220px}.drawer{width:360px}.job-row{grid-template-columns:150px minmax(0,1fr) 120px}}@media(min-width:1800px){:root{--nav-w:256px}.main{padding-left:32px;padding-right:32px}.grid.cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.card{padding:18px}.page h1{font-size:30px}} +body[data-theme='violet-core']{--bg:#070A13;--deep:#0B1020;--panel:#10172A;--panel2:#141D34;--panel3:#1A2540;--text:#F3F5FA;--muted:#A3ACC0;--line:rgba(155,121,255,.16);--line2:rgba(155,121,255,.36);--accent:#7C5CFF;--accent2:#AA76FF;--soft:rgba(124,92,255,.13);--ok:#4DBB84;--warn:#D8A84E;--bad:#D65A67;--info:#4DB7FF;--glass:rgba(7,10,19,.9);--shadow:0 18px 46px rgba(0,0,0,.34)} +/* AXDESK-A1: the Violet Core desktop shell. */ +:root{--rail-w:62px;--subnav-w:242px;--shell-dock-h:156px;--shell-inspector-w:326px} +.inspector-external-trigger{display:none} +.top{height:var(--top-h);padding:0 14px 0 16px;gap:10px}.brand{width:210px;flex:0 0 210px}.brand-name{letter-spacing:.04em}.top-tabs{display:flex;align-items:center;gap:3px;height:30px;border:1px solid var(--line);border-radius:8px;padding:3px;background:var(--deep);white-space:nowrap}.top-tab{padding:5px 9px;border-radius:5px;font-size:12px}.top-tab.active{background:var(--soft);color:var(--accent);font-weight:700}.muted-tab{color:var(--muted)}.command-btn{height:34px;border:1px solid var(--line2);border-radius:7px;background:var(--soft);color:var(--accent);font-size:12px;padding:0 10px;white-space:nowrap}.rail{position:fixed;left:0;top:var(--top-h);bottom:0;width:var(--rail-w);z-index:31;padding:10px 6px;border-right:1px solid var(--line);background:linear-gradient(180deg,var(--deep),var(--panel2));display:flex;flex-direction:column;gap:5px}.rail-item{display:grid;place-items:center;gap:2px;min-height:48px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--muted);font-size:10px;padding:5px 2px}.rail-item:hover{background:var(--panel3);color:var(--text)}.rail-item.active{border-color:var(--line2);background:var(--soft);color:var(--accent)}.rail-glyph{font-size:18px;line-height:1;font-family:var(--font-ui)}.subnav{left:var(--rail-w);width:var(--subnav-w);padding:13px 10px 18px;background:linear-gradient(180deg,var(--deep),var(--panel2));z-index:29}.subnav .nav-group{display:none}.subnav .nav-group[data-module].active-group{display:block}.main{margin-left:calc(var(--rail-w) + var(--subnav-w));padding:calc(var(--top-h) + 18px) calc(var(--shell-inspector-w) + 24px) calc(var(--shell-dock-h) + 28px) 24px}.inspector{position:fixed;right:0;top:var(--top-h);bottom:var(--shell-dock-h);width:var(--shell-inspector-w);z-index:28;border-left:1px solid var(--line);background:linear-gradient(180deg,var(--panel2),var(--deep));overflow:auto;transition:transform .18s ease}.inspector-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px;border-bottom:1px solid var(--line)}.inspector-head b{display:block;margin-top:4px}.inspector-body{padding:14px}.inspector-core{display:grid;gap:4px;padding:14px;border:1px solid var(--line2);border-radius:10px;background:radial-gradient(circle at 50% 0,var(--soft),transparent 65%),var(--panel);margin-bottom:14px}.core-orb{font-size:30px;color:var(--accent);text-shadow:0 0 22px var(--accent);line-height:1}.inspector-list{display:grid;gap:0;border:1px solid var(--line);border-radius:8px;overflow:hidden}.inspector-list>div{display:grid;gap:2px;padding:10px;border-bottom:1px solid var(--line)}.inspector-list>div:last-child{border-bottom:0}.inspector-list span{font:10px var(--mono);color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.inspector-list b{font-size:12px;overflow-wrap:anywhere}.small{font-size:11px}.activity-dock{position:fixed;left:calc(var(--rail-w) + var(--subnav-w));right:0;bottom:0;height:var(--shell-dock-h);z-index:27;border-top:1px solid var(--line2);background:var(--glass);backdrop-filter:blur(16px);transition:height .18s ease}.dock-head{height:42px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--line)}.dock-brand,.dock-actions{display:flex;align-items:center;gap:8px}.dock-pulse{width:7px;height:7px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 3px var(--soft),0 0 12px var(--accent)}.dock-body{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;padding:10px 14px}.dock-card{display:grid;gap:3px;min-width:0;padding:9px 11px;border:1px solid var(--line);border-radius:8px;background:var(--panel2)}.dock-card b{font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dock-card>span:last-child{font-size:11px;color:var(--muted)}.dock-label{font:10px var(--mono);color:var(--accent);letter-spacing:.08em}.dock-note{border-style:dashed}.toast{bottom:calc(var(--shell-dock-h) + 16px)}body.inspector-collapsed .inspector{transform:none;width:42px;overflow:hidden}.inspector-collapsed .inspector-head{padding:10px 4px;justify-content:center}.inspector-collapsed .inspector-head>div,.inspector-collapsed .inspector-body{display:none}body.inspector-collapsed .main{padding-right:24px}body.dock-collapsed{--shell-dock-h:42px}body.dock-collapsed .dock-body{display:none}.theme button.active{background:var(--soft);color:var(--accent);font-weight:700} +@media(max-width:1280px){:root{--rail-w:58px;--subnav-w:210px;--shell-inspector-w:280px}.brand{width:170px;flex-basis:170px}.top-tabs{display:none}.main{padding-right:calc(var(--shell-inspector-w) + 18px);padding-left:18px}.subnav .nav-title{display:flex}.subnav .nav-item .cn,.subnav .nav-item .en{display:inline}.rail-item{min-height:44px}.dock-body{grid-template-columns:repeat(3,minmax(0,1fr))}.dock-note{display:none}} +@media(max-width:960px){:root{--rail-w:54px;--subnav-w:0px;--shell-dock-h:116px}.subnav{width:min(242px,calc(100vw - var(--rail-w)));transform:translateX(calc(-100% - var(--rail-w)));transition:transform .18s ease;box-shadow:var(--shadow)}body.subnav-open .subnav{transform:none}.main{margin-left:var(--rail-w);padding:calc(var(--top-h) + 14px) 14px calc(var(--shell-dock-h) + 18px)}.inspector{width:min(326px,88vw);bottom:var(--shell-dock-h);transform:translateX(100%)}.inspector.open{transform:none}.inspector-external-trigger{display:inline-grid;place-items:center}.activity-dock{left:var(--rail-w)}.brand{width:38px;flex-basis:38px}.brand-name,.brand-sub,.workspace-pill,.chip,.user .meta,.search{display:none}.command-btn{margin-left:auto}.dock-body{grid-template-columns:repeat(3,minmax(0,1fr));padding:7px 10px}.dock-card{padding:7px}.dock-card:nth-child(3){display:none}} +@media(max-width:520px){:root{--top-h:54px;--shell-dock-h:106px}.top{padding:0 8px;gap:6px}.command-btn{font-size:0;width:34px;padding:0}.command-btn:before{content:'⌘';font-size:16px}.theme{display:none}.icon-btn{min-width:32px}.main{padding-left:10px;padding-right:10px}.page-head{align-items:flex-start;flex-direction:column}.head-actions{width:100%;flex-wrap:wrap}.grid.cols-2,.grid.cols-3,.grid.cols-4{grid-template-columns:1fr}.dock-head{padding:0 9px}.dock-body{grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;padding:6px 8px}.dock-card:nth-child(4){display:none}.dock-card b{font-size:11px}.dock-card>span:last-child{font-size:10px}.dock-brand .muted{display:none}} +@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}} +@media(max-width:960px){body.inspector-collapsed .inspector{width:min(326px,88vw);transform:translateX(100%)}} +body[data-theme='apple-light']{--bg:#F4F6FB;--deep:#E9EDF5;--panel:#FFFFFF;--panel2:#F8FAFD;--panel3:#EEF2F8;--text:#182033;--muted:#667085;--line:rgba(31,41,55,.12);--line2:rgba(91,75,183,.34);--accent:#5B4BB7;--accent2:#4338CA;--soft:rgba(91,75,183,.10);--ok:#167A52;--warn:#9A6700;--bad:#B42318;--info:#1769AA;--glass:rgba(255,255,255,.90);--shadow:0 18px 46px rgba(31,41,55,.14)} +body[data-theme='apple-light'] .app{background:radial-gradient(circle at 50% -20%,rgba(91,75,183,.10),transparent 36%),linear-gradient(180deg,var(--deep),var(--bg))} +.route-state{margin-left:auto;border:1px solid var(--line);border-radius:999px;padding:2px 6px;font:600 9px var(--mono);white-space:nowrap;color:var(--muted)} +.route-state-available{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 38%,transparent)} +.route-state-partial{color:var(--info);border-color:color-mix(in srgb,var(--info) 38%,transparent)} +.route-state-planned{color:var(--muted)} +.route-state-blocked{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 38%,transparent)} +@media(max-width:1280px){.route-state{display:none}.nav-item[data-route-state='available']::after,.nav-item[data-route-state='partial']::after,.nav-item[data-route-state='planned']::after,.nav-item[data-route-state='blocked']::after{content:'';width:5px;height:5px;border-radius:50%;margin-left:auto;background:var(--muted)}.nav-item[data-route-state='available']::after{background:var(--ok)}.nav-item[data-route-state='partial']::after{background:var(--info)}.nav-item[data-route-state='blocked']::after{background:var(--bad)}} diff --git a/app/workspace/ui/index.html b/app/workspace/ui/index.html index b34d508..b7a09dc 100644 --- a/app/workspace/ui/index.html +++ b/app/workspace/ui/index.html @@ -6,18 +6,31 @@ 元枢系统 ArcheAxis OS · 元枢·观心 - +
-
元枢系统
ARCHEAXIS OS
+
元枢桌面
ARCHEAXIS DESKTOP
+
观心工作台本地运行时+ 规划中
元枢·观心LOCAL ONLY
- + + + 本地工作台 -
+
AL
本地用户
--:--:--
- + +
GUANXIN OVERVIEW

观心总览

只展示本机数据库和发布清单可证明的状态;没有证据的数据不会出现在这里。

@@ -74,6 +87,14 @@
尚未接入真实数据

不会展示样例数量、虚构进度、伪服务状态或不可执行按钮。

+ +
+
活动坞真实本地状态
读取中
+
JOB读取中真实任务
DELIVERY读取中待投递 / 失败
REVIEW读取中待人工复核
TRUTH BOUNDARY不显示伪进度状态来自本地 API
+
diff --git a/desktop/src-tauri/src/runtime.rs b/desktop/src-tauri/src/runtime.rs index 6ddf78f..c40e76e 100644 --- a/desktop/src-tauri/src/runtime.rs +++ b/desktop/src-tauri/src/runtime.rs @@ -186,7 +186,10 @@ mod tests { resolved.data_dir.display(), ); assert!(resolved.isolated, "installed runtime must be isolated"); - assert_ne!(resolved.cwd, resolved.data_dir.parent().unwrap_or(&resolved.cwd)); + assert_ne!( + resolved.cwd, + resolved.data_dir.parent().unwrap_or(&resolved.cwd) + ); } #[test] @@ -198,8 +201,11 @@ mod tests { let python = resources.join("runtime/python/python.exe"); fs::create_dir_all(python.parent().expect("python parent")).expect("create runtime"); fs::create_dir_all(repository.join(".hermes")).expect("create project boundary"); - fs::write(repository.join("pyproject.toml"), b"[project]\nname = 'fixture'\n") - .expect("create project marker"); + fs::write( + repository.join("pyproject.toml"), + b"[project]\nname = 'fixture'\n", + ) + .expect("create project marker"); fs::write(&python, b"test").expect("create python marker"); let resolved = resolve_runtime(&resources, &resources, &local_data, false) diff --git a/docs/PRODUCT_POSITIONING.md b/docs/PRODUCT_POSITIONING.md new file mode 100644 index 0000000..e602f6b --- /dev/null +++ b/docs/PRODUCT_POSITIONING.md @@ -0,0 +1,61 @@ +# ArcheAxis OS 产品定位 + +> **ArcheAxis OS is a local-first, evidence-driven, bidirectional learning and knowledge system for individuals and AI.** + +> **同一份知识,人学得更深,AI 用得更准。** + +## 定位边界 + +ArcheAxis OS 的产品中心是受治理的学习与知识底座,而不是通用 Agent 平台。它将个人学习和 AI 使用连接到同一份可追溯的 Source、Claim、Evidence 与 Knowledge 之上。 + +本页定义对外定位和术语边界;它不宣称当前所有产品页面、Agent 功能或自动化能力已实现。当前可验证能力和限制以 [`PROJECT_STATUS.md`](PROJECT_STATUS.md) 与 [`VERIFICATION_POLICY.md`](VERIFICATION_POLICY.md) 为准。 + +## 一源、双学、双向反馈 + +```text +原始资料 / Source Asset / Evidence + │ + ▼ + 受治理的统一知识底座 + ┌─────┴─────┐ + ▼ ▼ + 个人学习链 AI 使用链 + │ │ + └─────┬─────┘ + ▼ + 人工审核与双向知识演进 +``` + +### 个人学习链 + +```text +资料 → 理解 → 结构化 → 课程/笔记 → 练习 → 复习 → 掌握 → 应用 → Teach Back → 迁移 +``` + +### AI 使用链 + +```text +Knowledge Unit → Retrieval → Context Pack → Machine Knowledge +→ Task Support → Tool / Agent → Trace → Evaluation → Candidate Lesson +``` + +## 治理原则 + +- 人的学习笔记、纠错、练习、人工标注和审批决定,先成为 Candidate。 +- AI 生成的新来源、Claim、冲突、解释、练习、任务结果和 Lesson,也先成为 Candidate。 +- Candidate 不能自动成为 verified truth,或自动进入 Runtime。 +- 用户页面只显示可由本地持久化 API 读回的状态;没有真实合同时必须清楚标明“尚未接入”。 +- Agent 是 AI 使用层,不是产品中心;稳定的知识、证据和学习治理不依赖特定模型或 Agent。 + +## 对外术语 + +| 不再作为对外主定位 | 对外推荐表达 | +| --- | --- | +| 认知操作系统 / Cognitive OS | Learning & Knowledge System | +| 通用认知 OS / Agent OS | 本地优先、证据驱动的学习知识系统 | +| 认知闭环 | 学习—知识—应用闭环 | +| 认知检查器 | 上下文与证据检查器 | +| 认知演化 | 双向反馈与知识演进 | +| Cognitive Workspace | Human–AI Learning Workspace | + +内部架构、历史文档和稳定 canonical ID 可继续保留兼容术语;任何术语替换都不得改变 API、数据库、迁移或 Release 状态。 diff --git a/docs/README.md b/docs/README.md index 9cb7caa..0c77e39 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,7 @@ ## 当前事实与未来方向 +- [`PRODUCT_POSITIONING.md`](PRODUCT_POSITIONING.md):ArcheAxis OS 对外产品定位、治理边界与术语。 - [`PROJECT_STATUS.md`](PROJECT_STATUS.md):当前验证状态、已知限制和质量门禁。 - [`FUTURE_EXECUTION_BLUEPRINT.md`](FUTURE_EXECUTION_BLUEPRINT.md):长期设计原则、候选轨道、延后项和进入执行门槛;不代表当前完成度。 - [`architecture/CURRENT_ARCHITECTURE.md`](architecture/CURRENT_ARCHITECTURE.md):当前运行时架构与模块边界。 diff --git a/scripts/a0_browser_smoke.py b/scripts/a0_browser_smoke.py index ec5d5d3..719f600 100644 --- a/scripts/a0_browser_smoke.py +++ b/scripts/a0_browser_smoke.py @@ -7,13 +7,14 @@ import sys from pathlib import Path -from playwright.sync_api import Page, Route, sync_playwright +from playwright.sync_api import Page, Route, expect, sync_playwright from runtime_http_smoke import running_core WORKSPACE_ROOT = "/" + "workspace" STATUS_PATTERN = f"**{WORKSPACE_ROOT}/api/status" INTAKE_PATTERN = f"**{WORKSPACE_ROOT}/api/intake/url" RESEARCH_PATTERN = f"**{WORKSPACE_ROOT}/api/research" +JOBS_PATTERN = f"**{WORKSPACE_ROOT}/api/jobs" def _partial_status(route: Route) -> None: @@ -52,6 +53,7 @@ def exercise_workspace(page: Page, base_url: str) -> None: page_errors: list[str] = [] page.on("console", lambda message: console_errors.append(message.text) if message.type == "error" else None) page.on("pageerror", lambda error: page_errors.append(str(error))) + page.emulate_media(reduced_motion="reduce") page.goto(f"{base_url}/workspace#overview]", wait_until="networkidle") assert page.url.endswith("#unavailable") @@ -63,16 +65,52 @@ def exercise_workspace(page: Page, base_url: str) -> None: page.get_by_role("heading", name="功能尚未接入").wait_for() page.goto(f"{base_url}/workspace#overview", wait_until="networkidle") + assert page.locator("body").get_attribute("data-theme") == "apple-light" + page.get_by_role("button", name="紫曜").click() + assert page.locator("body").get_attribute("data-theme") == "violet-core" + page.get_by_role("button", name="浅色").click() + assert page.locator("body").get_attribute("data-theme") == "apple-light" + assert page.get_by_role("complementary", name="一级模块").is_visible() + assert page.get_by_role("complementary", name="上下文与证据检查器").is_visible() + assert page.get_by_role("region", name="活动坞").is_visible() + assert page.get_by_role("button", name="折叠上下文与证据检查器").is_visible() + page.get_by_role("button", name="折叠上下文与证据检查器").click() + assert page.evaluate("document.body.classList.contains('inspector-collapsed')") + page.get_by_role("button", name="展开上下文与证据检查器").click() + assert not page.evaluate("document.body.classList.contains('inspector-collapsed')") + page.get_by_role("button", name="折叠活动坞").click() + assert page.evaluate("document.body.classList.contains('dock-collapsed')") + page.get_by_role("button", name="展开活动坞").click() + assert not page.evaluate("document.body.classList.contains('dock-collapsed')") + page.get_by_role("button", name="智体").click() + page.get_by_role("heading", name="Agent管理").wait_for() + assert "尚未接入真实数据" in page.locator("#page-unavailable").inner_text() + planned_route = page.locator('.nav-item[data-page="agents"]') + assert planned_route.get_attribute("data-route-state") == "planned" + assert planned_route.get_by_text("规划中", exact=True).is_visible() + page.locator('.rail-item[title="观心"]').click() + page.get_by_role("heading", name="观心总览").wait_for() page.get_by_text("异步Worker", exact=True).wait_for() assert "尚未实现" in page.locator("#capability-summary").inner_text() + page.locator('.rail-item[title="知行"]').click() + delivery_route = page.locator('.nav-item[data-page="delivery"]') + assert delivery_route.get_attribute("data-route-state") == "partial" + assert delivery_route.get_by_text("部分接入", exact=True).is_visible() + delivery_route.get_by_text("投递回执", exact=True).click() + page.get_by_role("heading", name="投递回执").wait_for() + assert page.url.endswith("#delivery") + + page.locator('.rail-item[title="藏识"]').click() + learning_route = page.locator('.nav-item[data-page="learning"]') + learning_route.get_by_text("学习路线", exact=True).click() + page.get_by_role("heading", name="学习路线").wait_for() + assert page.url.endswith("#learning") page.route(STATUS_PATTERN, _partial_status) page.goto(f"{base_url}/workspace#diagnostics", wait_until="networkidle") page.get_by_role("button", name="刷新").click() page.get_by_text("系统状态", exact=True).wait_for() assert "本地状态读取失败" in page.locator("#diagnostics-summary").inner_text() - assert "能力状态" in page.locator("#capability-summary").inner_text() - assert "异步Worker" not in page.locator("#capability-summary").inner_text() page.unroute(STATUS_PATTERN, _partial_status) page.goto(f"{base_url}/workspace#overview", wait_until="networkidle") @@ -94,9 +132,18 @@ def exercise_workspace(page: Page, base_url: str) -> None: page.unroute(INTAKE_PATTERN, _network_failure) page.get_by_label("网页地址").fill("https://example.com/truth") + activity_reads: list[str] = [] + + def observe_activity_jobs(route: Route) -> None: + activity_reads.append("jobs") + route.continue_() + + page.route(JOBS_PATTERN, observe_activity_jobs) + activity_reads.clear() page.route(INTAKE_PATTERN, _intake_success) page.get_by_role("button", name="导入网页或GitHub仓库").click() page.locator("#intake-result").filter(has_text="下一步:等待人工复核").wait_for() + assert "jobs" in activity_reads result = page.locator("#intake-result").inner_text() assert "来源记录:1" in result assert "候选要点:2" in result @@ -104,6 +151,7 @@ def exercise_workspace(page: Page, base_url: str) -> None: assert "引擎:自动" not in result assert "内容长度:0" not in result assert all(identifier not in result for identifier in ("package_id", "job_id", "command_id")) + page.unroute(JOBS_PATTERN, observe_activity_jobs) page.unroute(INTAKE_PATTERN, _intake_success) page.get_by_label("网页地址").fill("https://example.com/partial") @@ -125,9 +173,104 @@ def exercise_workspace(page: Page, base_url: str) -> None: assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth") page.set_viewport_size({"width": 390, "height": 844}) - page.goto(f"{base_url}/workspace#overview", wait_until="networkidle") + page.evaluate("localStorage.removeItem('aa-inspector')") + page.reload(wait_until="networkidle") assert page.get_by_role("heading", name="观心总览").is_visible() assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth") + assert page.evaluate("document.body.classList.contains('inspector-collapsed')") + inspector = page.locator("#inspector") + assert not inspector.evaluate("element => element.classList.contains('open')") + external_inspector_trigger = page.locator(".inspector-external-trigger") + expect(external_inspector_trigger).to_be_visible() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "false") + assert inspector.get_attribute("aria-hidden") == "true" + assert inspector.evaluate("element => element.hasAttribute('inert')") + external_inspector_trigger.click() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "true") + assert page.evaluate("document.activeElement?.classList.contains('inspector-internal-trigger')") + assert inspector.get_attribute("aria-hidden") == "false" + assert not inspector.evaluate("element => element.hasAttribute('inert')") + page.locator(".inspector-internal-trigger").click() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "false") + assert page.evaluate("document.activeElement?.classList.contains('inspector-external-trigger')") + assert inspector.get_attribute("aria-hidden") == "true" + assert inspector.evaluate("element => element.hasAttribute('inert')") + # A mobile-closed Inspector must become operable when the same document + # crosses to desktop. Desktop keeps its collapsed panel in the accessibility + # tree so its internal reopen trigger remains usable. + page.set_viewport_size({"width": 1280, "height": 800}) + expect(inspector).to_have_attribute("aria-hidden", "false") + assert not inspector.evaluate("element => element.hasAttribute('inert')") + internal_inspector_trigger = page.locator(".inspector-internal-trigger") + expect(internal_inspector_trigger).to_be_visible() + internal_inspector_trigger.click() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "true") + assert inspector.evaluate("element => element.classList.contains('open')") + + # A desktop-collapsed Inspector must become genuinely inaccessible off-canvas + # on mobile, while its external trigger remains an actual reopen path. + internal_inspector_trigger.click() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "false") + page.set_viewport_size({"width": 390, "height": 844}) + expect(inspector).to_have_attribute("aria-hidden", "true") + assert inspector.evaluate("element => element.hasAttribute('inert')") + inspector_geometry = page.evaluate( + """() => { const rect = document.querySelector('#inspector').getBoundingClientRect(); return { left: rect.left, viewportWidth: window.innerWidth }; }""" + ) + assert inspector_geometry["left"] >= inspector_geometry["viewportWidth"], inspector_geometry + expect(external_inspector_trigger).to_be_visible() + external_inspector_trigger.click() + expect(external_inspector_trigger).to_have_attribute("aria-expanded", "true") + assert inspector.get_attribute("aria-hidden") == "false" + assert not inspector.evaluate("element => element.hasAttribute('inert')") + + page.locator('.rail-item[title="知行"]').click() + assert page.evaluate("document.body.classList.contains('subnav-open')") + assert page.locator('.rail-item[title="知行"]').get_attribute("aria-expanded") == "true" + assert page.locator("#nav").get_attribute("aria-hidden") == "false" + assert not page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + assert page.evaluate("document.activeElement?.dataset.page") == "runtime" + page.locator('.nav-item[data-page="delivery"]').click() + page.get_by_role("heading", name="投递回执").wait_for() + assert page.url.endswith("#delivery") + assert not page.evaluate("document.body.classList.contains('subnav-open')") + assert page.evaluate("document.activeElement?.getAttribute('title')") == "知行" + assert page.locator('.rail-item[title="知行"]').get_attribute("aria-expanded") == "false" + assert page.locator("#nav").get_attribute("aria-hidden") == "true" + assert page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + + page.locator('.rail-item[title="藏识"]').click() + assert page.evaluate("document.body.classList.contains('subnav-open')") + page.locator('.nav-item[data-page="learning"]').click() + page.get_by_role("heading", name="学习路线").wait_for() + assert page.url.endswith("#learning") + assert not page.evaluate("document.body.classList.contains('subnav-open')") + + page.locator('.rail-item[title="藏识"]').click() + assert page.evaluate("document.body.classList.contains('subnav-open')") + page.keyboard.press("Escape") + assert not page.evaluate("document.body.classList.contains('subnav-open')") + assert page.evaluate("document.activeElement?.getAttribute('title')") == "藏识" + assert page.locator('.rail-item[title="藏识"]').get_attribute("aria-expanded") == "false" + assert page.locator("#nav").get_attribute("aria-hidden") == "true" + assert page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + + # Cross the responsive breakpoint without reloading. Desktop exposes the + # active module, while mobile closes and removes its routes from focus. + page.set_viewport_size({"width": 1280, "height": 800}) + expect(page.locator("#nav")).to_have_attribute("aria-hidden", "false") + assert not page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + assert page.locator('.rail-item[title="藏识"]').get_attribute("aria-expanded") == "true" + page.locator('.nav-item[data-page="learning"]').focus() + page.set_viewport_size({"width": 390, "height": 844}) + expect(page.locator("#nav")).to_have_attribute("aria-hidden", "true") + assert page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + assert page.evaluate("document.activeElement?.getAttribute('title')") == "藏识" + assert page.locator('.rail-item[title="藏识"]').get_attribute("aria-expanded") == "false" + page.set_viewport_size({"width": 1280, "height": 800}) + expect(page.locator("#nav")).to_have_attribute("aria-hidden", "false") + assert not page.locator("#nav").evaluate("element => element.hasAttribute('inert')") + assert page.locator('.rail-item[title="藏识"]').get_attribute("aria-expanded") == "true" assert not page_errors, page_errors assert not console_errors, console_errors