fix(education): ship 0.2.1 drag and Forgejo truth state

This commit is contained in:
冰朔 2026-08-08 09:54:45 +08:00
commit 28f68fa9ac
15 changed files with 171 additions and 17 deletions

View file

@ -1,7 +1,7 @@
{
"name": "guanghu-education-industry-os",
"private": true,
"version": "0.2.0",
"version": "0.2.1",
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 5216",

View file

@ -1171,7 +1171,7 @@ dependencies = [
[[package]]
name = "guanghu-education-industry-os"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"serde",
"serde_json",

View file

@ -1,6 +1,6 @@
[package]
name = "guanghu-education-industry-os"
version = "0.2.0"
version = "0.2.1"
description = "Guanghu Education Subdomain operating system client"
authors = ["Guanghu"]
license = "LicenseRef-Guanghu"

View file

@ -3,5 +3,5 @@
"identifier": "default",
"description": "Base window capability for the Guanghu education workspace",
"windows": ["main"],
"permissions": ["core:default"]
"permissions": ["core:default", "core:window:allow-start-dragging"]
}

View file

@ -1 +1 @@
{"default":{"identifier":"default","description":"Base window capability for the Guanghu education workspace","local":true,"windows":["main"],"permissions":["core:default"]}}
{"default":{"identifier":"default","description":"Base window capability for the Guanghu education workspace","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-start-dragging"]}}

View file

@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "光湖分域 · 教育",
"version": "0.2.0",
"version": "0.2.1",
"identifier": "com.guanghulab.subdomain.education",
"build": {
"frontendDist": "../dist",

View file

@ -1,7 +1,9 @@
import { invoke } from '@tauri-apps/api/core'
import { useMemo, useState } from 'react'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { useMemo, useState, type MouseEvent } from 'react'
import {
bundledModules,
formatForgejoVersion,
parseSheetMetrics,
routeInstruction,
type InstructionRoute,
@ -72,6 +74,21 @@ function LakeMark() {
return <span className="lake-mark" aria-hidden="true"><i /><b /></span>
}
function WindowDragHandle() {
const startDragging = (event: MouseEvent<HTMLDivElement>) => {
if (event.button !== 0) return
event.preventDefault()
void getCurrentWindow().startDragging()
}
return <div
className="native-drag-handle"
data-tauri-drag-region
aria-label="拖动窗口"
onMouseDown={startDragging}
/>
}
function LoginGate({
error,
loading,
@ -82,8 +99,8 @@ function LoginGate({
onConnect: () => void
}) {
return <main className="login-gate">
<WindowDragHandle />
<section className="login-window">
<div className="window-drag" data-tauri-drag-region />
<LakeMark />
<p> · </p>
<h1></h1>
@ -117,6 +134,8 @@ function StatusCard({ label, value, detail, tone = 'green' }: {
function ChannelHome({ snapshot, modules }: { snapshot: ChannelSnapshot; modules: ModuleEntry[] }) {
const live = modules.filter((module) => module.state === 'LIVE_STAGE_1').length
const brainCandidates = modules.filter((module) => module.group === 'persona-brain').length
const liveAgent = modules.some((module) => module.group === 'persona-brain' && module.state === 'LIVE_STAGE_1')
const mounted = snapshot.channel.mountedModules?.length ?? 0
const repositoryCount = snapshot.codeChannel.repositories?.data?.length ?? 0
return <div className="page-stack">
@ -129,7 +148,8 @@ function ChannelHome({ snapshot, modules }: { snapshot: ChannelSnapshot; modules
<StatusCard label="个人服务器" value={String(snapshot.node.state ?? '已连接')} detail="身份与现实数据锚点 · XX-GZ-001" />
<StatusCard label="第五域协议" value={String(snapshot.fifthDomainLink.state ?? '未知')} detail="上游导航 · JD-FD-PRIMARY" tone="blue" />
<StatusCard label="真实能力" value={`${live}`} detail={`模板已装配 ${mounted} 个模块地址`} tone="amber" />
<StatusCard label="代码频道" value={snapshot.codeChannel.version?.version ?? '未知'} detail={`${repositoryCount} 个服务器内仓库`} tone="blue" />
<StatusCard label="代码频道" value={formatForgejoVersion(snapshot.codeChannel.version?.version)} detail={`${repositoryCount} 个服务器内仓库 · Forgejo 数据面`} tone="blue" />
<StatusCard label="人格体 Agent" value={liveAgent ? '已接入' : '未接入'} detail={`仅登记 ${brainCandidates} 个研究候选`} tone="amber" />
</section>
<section className="work-grid">
<article className="work-panel">
@ -303,6 +323,7 @@ function App() {
if (!snapshot) return <LoginGate error={error} loading={connecting} onConnect={connect} />
return <main className="education-os">
<WindowDragHandle />
<aside className="side-rail">
<div className="brand"><LakeMark /><span><strong> · </strong><small>EDUCATION SUBDOMAIN OS</small></span></div>
<nav>{navigation.map((item) => <button key={item.id} className={surface === item.id ? 'active' : ''} onClick={() => setSurface(item.id)}>

View file

@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { bundledModules, parseSheetMetrics, routeInstruction } from './guanghuKernel'
import { bundledModules, formatForgejoVersion, parseSheetMetrics, routeInstruction } from './guanghuKernel'
describe('Guanghu Education Subdomain kernel', () => {
it.each([
@ -24,4 +24,9 @@ describe('Guanghu Education Subdomain kernel', () => {
{ label: '周子航', value: 65 },
])
})
it('presents the Forgejo release without exposing the Gitea compatibility suffix as product identity', () => {
expect(formatForgejoVersion('16.0.1+gitea-1.22.0')).toBe('Forgejo 16.0.1')
expect(formatForgejoVersion()).toBe('Forgejo')
})
})

View file

@ -54,6 +54,11 @@ export function bundledModules(): ModuleEntry[] {
return registry.modules as ModuleEntry[]
}
export function formatForgejoVersion(rawVersion?: string): string {
const release = rawVersion?.match(/^(?:forgejo\s+version\s+)?([0-9]+(?:\.[0-9]+){2})/i)?.[1]
return release ? `Forgejo ${release}` : 'Forgejo'
}
export function parseSheetMetrics(content: string): Array<{ label: string; value: number }> {
return content
.trim()

View file

@ -10,6 +10,18 @@ body { margin: 0; min-width: 900px; min-height: 100vh; overflow: hidden; }
button, input, textarea { font: inherit; }
button { color: inherit; }
.native-drag-handle {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 82px;
height: 34px;
cursor: grab;
-webkit-app-region: drag;
}
.native-drag-handle:active { cursor: grabbing; }
.login-gate {
display: grid;
min-height: 100vh;
@ -29,7 +41,6 @@ button { color: inherit; }
box-shadow: 0 30px 90px rgba(0, 0, 0, .38), inset 0 1px rgba(255,255,255,.025);
text-align: center;
}
.window-drag { position: absolute; inset: 0 0 auto; height: 42px; border-radius: 18px 18px 0 0; }
.login-window > .lake-mark { width: 58px; height: 58px; margin-bottom: 18px; transform: scale(1.65); }
.login-window > p { margin: 0 0 8px; color: #6ec2f4; font-size: 12px; letter-spacing: .18em; }
.login-window h1 { margin: 0; color: #f0f7fc; font-size: 31px; font-weight: 650; letter-spacing: -.03em; }
@ -115,7 +126,7 @@ button { color: inherit; }
.page-intro { padding: 2px 2px 9px; }.page-intro p, .editor-page header p { margin: 0 0 7px; color: #4aa9e8; font-size: 9px; letter-spacing: .08em; }
.page-intro h2, .editor-page h2 { margin: 0; color: #edf5fa; font-size: 25px; font-weight: 650; letter-spacing: -.025em; }
.page-intro span, .editor-page header span { display: block; margin-top: 7px; color: #6c8395; font-size: 11px; }
.status-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.status-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
.status-card { display: grid; gap: 7px; min-height: 112px; padding: 15px 16px; border: 1px solid var(--line); background: linear-gradient(180deg, rgba(10, 27, 42, .9), rgba(7, 21, 33, .9)); }
.status-card span { color: #71899b; font-size: 9px; }.status-card strong { color: #62d0bc; font-size: 20px; }.status-card small { color: #526b7f; font-size: 8px; }
.status-card--blue strong { color: #64b8ef; }.status-card--amber strong { color: #d5b463; }

View file

@ -1,4 +1,11 @@
import { defineConfig } from 'vite'
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({ plugins: [react()], clearScreen: false, server: { strictPort: true } })
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: { strictPort: true },
test: {
exclude: ['**/node_modules/**', '**/dist/**', '**/._*'],
},
})