Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8fb6eda
docs(shared): 同步 Peer 运行身份契约
xiaoland Sep 22, 2026
18ae924
feat(peer): 分离本地设置与 Peer 管理
xiaoland Sep 22, 2026
de21c18
feat(extension): 提供 Registry 发现与部署级安装
xiaoland Sep 22, 2026
009dff2
fix(extension): 兼容 Node 22 候选安装
xiaoland Sep 22, 2026
debc81b
fix(extension): 使用候选源码类型入口
xiaoland Sep 22, 2026
9b18e87
feat(web): edit Source and Peer config with visual forms
xiaoland Sep 23, 2026
dcc5ecd
test: select JSON mode for peer draft E2E
xiaoland Sep 23, 2026
e58e1be
test: reopen peer draft in JSON mode
xiaoland Sep 23, 2026
6225121
feat: hand off extension discovery to Registry Web
xiaoland Sep 23, 2026
27d4449
docs: update PR verification for Registry handoff
xiaoland Sep 23, 2026
873ad84
style: format task packet
xiaoland Sep 23, 2026
1e23d96
docs: record live Registry handoff acceptance
xiaoland Sep 23, 2026
dfc2f29
feat: manage Extension enablement across selected Peers
xiaoland Sep 23, 2026
b60fdf2
fix: use shared Peer liveness for Extension management
xiaoland Sep 23, 2026
2405c85
chore: consume InkUI 2.1.1 across Web and Extensions
xiaoland Sep 23, 2026
e513ec3
docs: record Extension acceptance and delivery boundary
xiaoland Sep 23, 2026
b71e9b3
docs: format Extension task packet
xiaoland Sep 23, 2026
1727485
fix: inherit InkUI text color for native links
xiaoland Sep 23, 2026
ee3760f
Allow removing offline Peers from the Web app
xiaoland Sep 23, 2026
81f2822
feat: 改善第四组交互反馈与页面层级
xiaoland Sep 23, 2026
28f9e11
chore: 接入正式 UI 2.2.0 并记录第四组交付
xiaoland Sep 23, 2026
c924f68
fix: 首次渲染前提供完整本地化文案
xiaoland Sep 23, 2026
1dd5d02
test: 在页面主体首次出现时检查本地化文案
xiaoland Sep 23, 2026
17e09a5
docs: 归档第四组正式发布与预览验收证据
xiaoland Sep 23, 2026
1123105
style: 格式化任务状态表格
xiaoland Sep 23, 2026
acbd83f
fix(web): Settings 不再展示 Peer ID
xiaoland Sep 23, 2026
3c0d95b
fix(web): 为启动等待与失败提供恢复界面
xiaoland Sep 23, 2026
90a8175
docs(task): 更新最终交付与启动恢复验收状态
xiaoland Sep 23, 2026
dedd8c7
test(web): 在终态 API 失败边界验证启动恢复
xiaoland Sep 23, 2026
632b3ea
test(web): 使用不可重试的凭据拒绝验证启动恢复
xiaoland Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/interaction-quality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@inkcre/client-web': patch
'@inkcre/core': patch
---

按任务精简 Settings、Peer 与 Extension 界面,统一页面和浏览器标题,补齐场景化加载、刷新失败保留、保存与取消的动作反馈。修正重复检索、任务不存在及日志读取失败的可观察结果,保留失败后的恢复入口。
10 changes: 10 additions & 0 deletions .changeset/schema-forms-tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@inkcre/client-web': patch
'@inkcre/core': patch
'@inkcre/ext-dev-utils': patch
'@inkcre/ext-mail': patch
'@inkcre/ext-memos': patch
'@inkcre/ext-twitter': patch
---

Source 与 Peer 配置使用可视化表单,保留完整 JSON 编辑模式;Peers 页面可确认删除离线 Peer 并清理其扩展启用记录。Extension 浏览与版本选择交由 Registry,Web 接收精确版本并确认安装;Web 与 Module Federation 扩展统一升级正式 UI 2.2.0,Peer AI 凭据在 schema 中标记为密码输入。
4 changes: 2 additions & 2 deletions apps/client-web/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Static Vue application and equal InKCre database Peer.
## Local Hazards

- Keep deployable artifacts environment-neutral: no service origin, Peer identity, JWT secret, Worker runtime, or `/api/config` fallback.
- Browser-owned settings are runtime authority. Keep the JWT signing secret masked, memory-only after use, absent from logs, and excluded from portable exports.
- Use **Peer** in technical contracts; product-facing UI may say **client**.
- Browser-owned settings are runtime authority. Keep the JWT signing secret masked during ordinary display and absent from logs. The explicit full-browser export includes it so another browser can restore the same experience.
- Use **Peer** for InKCre runtime nodes in both technical contracts and product UI. Keep “client” only for actual application or protocol client roles.
- Preserve exact capability delegation and typed unknown outcomes; do not replace them with a generic Core API path or automatic retry after ambiguous dispatch.
- Start development through root commands so SVC and Portless preserve worktree identity. The sibling UI source lane is explicitly non-release evidence.

Expand Down
4 changes: 2 additions & 2 deletions apps/client-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
},
"dependencies": {
"@inkcre/core": "workspace:*",
"@inkcre/extension-runtime-client-web": "https://github.com/InKCre/ext-reg/releases/download/runtime-client-web-v0.2.0/inkcre-extension-runtime-client-web-0.2.0.tgz",
"@inkcre/ui-web": "2.0.1",
"@inkcre/extension-runtime-client-web": "github:InKCre/ext-reg#57dcd80289285620a1fd27a0a48027e56c4ce7b1&path:/runtimes/client-web",
"@inkcre/ui-web": "2.2.0",
"@module-federation/runtime": "^0.21.4",
"@supabase/postgrest-js": "^2.84.0",
"@vue-flow/core": "^1.48.1",
Expand Down
128 changes: 124 additions & 4 deletions apps/client-web/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,95 @@
<script setup lang="ts">
import { ref, provide } from 'vue'
import { InkHeader } from '@inkcre/ui-web'
import { computed, ref, provide, watchEffect, onMounted } from 'vue'
import { InkButton, InkHeader, InkLoading } from '@inkcre/ui-web'
import AppSidePanel from './components/common/AppSidePanel/AppSidePanel.vue'
import router from './router'
import { createInkRouterAdapter } from './router'
import { INK_ROUTER_KEY } from '@inkcre/ui-web'
import { useRoute } from 'vue-router'
import RecallSearch from './components/recall/RecallSearch.vue'
import { useI18n } from 'vue-i18n'
import { configStore } from '@inkcre/core'
import {
currentWebPeer,
initializeCore,
shouldLoadPeerConfigAtBootstrap,
getExtensionHost,
startExtensionHost,
shutdownCore,
} from './core'
import { pageObjectTitle } from './composables/use-page-object-title'

provide(INK_ROUTER_KEY, createInkRouterAdapter(router, useRoute()))
const route = useRoute()
const { t } = useI18n()
const startupState = ref<'loading' | 'ready' | 'failed'>('loading')
const reload = () => window.location.reload()

onMounted(async () => {
const loadPeerConfig = shouldLoadPeerConfigAtBootstrap(window.location.pathname)
try {
await initializeCore({ loadPeerConfig })
} catch (error) {
console.error('[Core] Startup failed:', error)
startupState.value = 'failed'
return
}
startupState.value = 'ready'

const extensionHost = getExtensionHost()
if (loadPeerConfig) {
startExtensionHost().catch((error) => {
console.error('[Web Extension Host] Startup failed:', error)
})
}
window.addEventListener('beforeunload', () => {
// Browsers may end the page before cleanup completes, but never deliberately
// dispose Extension resources while our Job handlers are still draining.
void shutdownCore()
.then(() => extensionHost.shutdown())
.catch((error: unknown) => console.error('[Core] Shutdown failed', error))
})
})

const pageLabel = computed(() =>
t(typeof route.meta.titleKey === 'string' ? route.meta.titleKey : 'sidePanel.infoBase')
)
provide(INK_ROUTER_KEY, createInkRouterAdapter(router, route))
watchEffect(() => {
const parts: string[] = []
const objectTitle =
pageObjectTitle.value?.path === route.fullPath ? pageObjectTitle.value.value : undefined
if (route.name === 'Source')
parts.push(objectTitle || `${t('navigation.source')} #${route.params.id}`, pageLabel.value)
else if (route.name === 'Job') parts.push(`${t('navigation.job')} #${route.params.id}`)
else if (route.name === 'Extensions')
parts.push(...(objectTitle ? [objectTitle] : []), pageLabel.value)
else if (route.params.block || route.params.relation) {
const kind = route.params.relation
? 'relation'
: String(route.name).endsWith('SolvedContent')
? 'content'
: 'block'
parts.push(`${t(`navigation.${kind}`)} #${route.params.relation || route.params.block}`)
if (route.path.startsWith('/info-base/graph')) parts.push(t('navigation.graph'))
} else if (route.name !== 'InfoBaseListOverview' && route.name) parts.push(pageLabel.value)
else if (
configStore.metaConfig.INKCRE_PGREST_URL &&
currentWebPeer.value?.id === configStore.metaConfig.INKCRE_PEER_ID &&
currentWebPeer.value.name
)
parts.push(currentWebPeer.value.name)
document.title = [...parts, 'InKCre'].join(' - ')
})

// --- data ---
const sidebarExpanded = ref(false)
</script>

<template>
<div class="app">
<div v-if="startupState === 'ready'" class="app">
<InkHeader
title="InKCre"
:page-title="pageLabel"
logo-src="/logo/32.svg"
@menu-click="sidebarExpanded = !sidebarExpanded"
@title-click="router.push('/')"
Expand All @@ -28,9 +100,53 @@ const sidebarExpanded = ref(false)
</div>
<RecallSearch />
</div>
<main v-else class="app-startup">
<h1>InKCre</h1>
<InkLoading v-if="startupState === 'loading'" :label="t('startup.loading')" />
<p v-else role="alert">{{ t('startup.failed') }}</p>
<div class="app-startup__actions">
<InkButton v-if="startupState === 'failed'" @click="reload">
{{ t('common.retry') }}
</InkButton>
<!-- Full navigation leaves the partial runtime and opens Settings' recovery bootstrap. -->
<a href="/settings">{{ t('common.settings') }}</a>
</div>
</main>
</template>

<style lang="scss" scoped>
@use '@inkcre/ui-web/styles/functions' as *;
@use '@inkcre/ui-web/styles/mixins' as *;

.app-startup {
box-sizing: border-box;
min-height: 100dvh;
padding: sys-var(space, lg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: sys-var(space, md);
text-align: center;

h1 {
@include apply-font(title-lg);
margin: 0;
}

p {
margin: 0;
}

&__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: sys-var(space, md);
}
}

.app {
display: flex;
flex-direction: column;
Expand All @@ -56,4 +172,8 @@ body {
color: var(--sys-color-text-base);
background: var(--sys-color-surface-base);
}

a {
color: inherit;
}
</style>
2 changes: 1 addition & 1 deletion apps/client-web/src/components/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Scope: `apps/client-web/src/components/**`.
component-local copy of a model merely to shape props or emits.
- Solved-content renderer selection belongs to the core `Resolver` registration. Keep
`SolvedContentRenderer` as a dynamic handoff; do not add a second resolver-to-component map here.
- Keep technical names under `peer/`; user-facing copy may use “client”.
- Keep InKCre runtime-node names under `peer/` and use “Peer” in user-facing copy. Use “client” only for actual application or protocol client roles.

General component file guidance lives in
[component.instructions.md](../../../../.github/instructions/component.instructions.md). Stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const onSettingsClick = () => {
router.push('/settings')
}

const onPeersClick = () => {
router.push('/peers')
}

const onExploreClick = () => {
router.push('/info-base/graph')
}
Expand All @@ -43,6 +47,7 @@ watch(
<h2 class="app-side-panel__title">{{ t('sidePanel.managing') }}</h2>
<InkButton :text="t('sidePanel.sources')" class="w-full" @click="onSourcesClick" />
<InkButton :text="t('sidePanel.extensions')" class="w-full" @click="onExtensionsClick" />
<InkButton :text="t('sidePanel.peers')" class="w-full" @click="onPeersClick" />
<InkButton :text="t('sidePanel.settings')" class="w-full" @click="onSettingsClick" />
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
# ExtensionCard

A component that displays extension information and provides controls for toggling enable/disable status and editing configuration.
A component that displays an installed Extension and opens focused Peer selection for enablement changes.

## Props

- `extension` (InstalledExtension, required): the canonical installed row
- `enabled` (boolean, required): whether the selected Client's Peer UUID is in `enabled[]`
- `controlsCurrentWebRuntime` (boolean, required): whether the switch owns this browser's runtime
- `canChangeVersion` (boolean, required): whether the selected Host can validate a version change
- `changeVersion` (function, required): application-level version change through the selected Host
- `setEnabled` (function, required): application-level selected-Client control operation
- `peers` (Peer[], required): available Peers, including this browser
- `livePeerIds` (ReadonlySet<string>, required): server-evaluated online Peers used for management routing
- `currentPeerId` (string, required): identifies this browser's running Host
- `peerSelectionDisabled` (boolean): blocks actions while the Peer list is unavailable

## Emits

- `updated`: Emitted with the canonical row after configuration or version changes
- `updated`: Emitted with the canonical row after Peer, configuration, or version changes
- `uninstalled`: Emitted after the canonical row is removed

## Features

- Display canonical Extension Name, exact version, and optional nickname
- Toggle enable/disable status with a switch
- Choose one or more eligible Peers in an enable or disable dialog. Each selected Peer uses the existing current Host, remote Host, or durable-intent path. Partial failures remain visible and trigger a fresh installed-row read; no batch rollback or automatic retry occurs.
- Mount an Extension-owned setup contribution from this browser's running Web Distribution
- Keep setup availability independent of which Client is selected for enablement control
- Keep setup availability independent of the Peer selection dialog; disabling this browser closes its setup surface first
- Link the exact installed release's available global, Core, and Web documentation even when the extension is disabled or has no browser distribution; distinguish missing documentation from failed discovery
- Edit extension configuration via JSON editor in a dialog
- Change the exact shared version through the selected Host only while every Peer is disabled
- Select a published Registry Release and change the exact shared version only while every Peer is disabled
- Auto-formats configuration as JSON for easier editing
- Prevents uninstall while any Peer remains enabled
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@
flex-wrap: wrap;
gap: sys-var(space, sm);
}
&__details {
@include apply-font(body-sm);
summary {
cursor: pointer;
}
&[open] > summary {
margin-bottom: sys-var(space, sm);
}
.extension-card__hint {
margin-top: sys-var(space, sm);
}
}

&__hint {
@include apply-font(body-sm);
Expand All @@ -49,6 +61,38 @@
color: sys-var(color, feedback, error);
margin: 0;
}

&__peer-options {
display: flex;
flex-direction: column;
gap: sys-var(space, sm);
max-height: min(50vh, 24rem);
overflow-y: auto;
}

&__peer-option {
display: flex;
align-items: start;
gap: sys-var(space, sm);
padding: sys-var(space, sm);
border: 1px solid sys-var(color, border, subtle);
cursor: pointer;

input {
margin-top: 0.15em;
}
}

&__peer-note {
display: block;
color: sys-var(color, text, subtle);
}

&__loading {
display: grid;
min-height: 5rem;
place-items: center;
}
}

.config-popup {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { type PropType } from 'vue'
import type { InstalledExtension } from '@inkcre/core'
import type { InstalledExtension, Peer } from '@inkcre/core'

// --- Props ---
export const extensionCardProps = {
extension: { type: Object as PropType<InstalledExtension>, required: true },
enabled: { type: Boolean, required: true },
controlsCurrentWebRuntime: { type: Boolean, required: true },
canChangeVersion: { type: Boolean, required: true },
changeVersion: {
type: Function as PropType<(version: string) => Promise<InstalledExtension>>,
required: true,
},
setEnabled: {
type: Function as PropType<(enabled: boolean) => Promise<InstalledExtension>>,
required: true,
},
peers: { type: Array as PropType<Peer[]>, required: true },
livePeerIds: { type: Object as PropType<ReadonlySet<string>>, required: true },
currentPeerId: { type: String, required: true },
peerSelectionDisabled: { type: Boolean, default: false },
} as const

// --- Emits ---
Expand Down
Loading
Loading