feat(task4): refactor miniapp static serving and frontend bundle/tests
This commit is contained in:
parent
75ea77e206
commit
2f156f4ef5
20 changed files with 6448 additions and 2204 deletions
21
.github/workflows/pr.yml
vendored
21
.github/workflows/pr.yml
vendored
|
|
@ -16,6 +16,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
- name: Run go generate
|
- name: Run go generate
|
||||||
run: go generate ./...
|
run: go generate ./...
|
||||||
|
|
||||||
|
|
@ -36,8 +39,26 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '24'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
- name: Run go generate
|
- name: Run go generate
|
||||||
run: go generate ./...
|
run: go generate ./...
|
||||||
|
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: |
|
||||||
|
pnpm --dir pkg/miniapp/frontend install --frozen-lockfile
|
||||||
|
pnpm --dir pkg/miniapp/frontend test
|
||||||
|
|
||||||
- name: Run go test
|
- name: Run go test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -44,6 +44,14 @@ tasks/
|
||||||
|
|
||||||
# Added by goreleaser init:
|
# Added by goreleaser init:
|
||||||
dist/
|
dist/
|
||||||
|
!pkg/miniapp/static/dist/
|
||||||
|
!pkg/miniapp/static/dist/**
|
||||||
|
|
||||||
# Windows Application Icon/Resource
|
# Windows Application Icon/Resource
|
||||||
*.syso
|
*.syso
|
||||||
|
|
||||||
|
|
||||||
|
# Frontend dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,9 @@ Lint: `golangci-lint run`
|
||||||
| [`todo/TASKS-1.md`](todo/TASKS-1.md) | ~~**Memory & Performance Optimization**~~ ✅ 実装済み(MemoryStore キャッシュ+パース済み state、FunctionCall.Arguments map統一、ToolDefinition.Parameters RawMessage化、検索結果フォーマット共通化、stats 定期フラッシュ) |
|
| [`todo/TASKS-1.md`](todo/TASKS-1.md) | ~~**Memory & Performance Optimization**~~ ✅ 実装済み(MemoryStore キャッシュ+パース済み state、FunctionCall.Arguments map統一、ToolDefinition.Parameters RawMessage化、検索結果フォーマット共通化、stats 定期フラッシュ) |
|
||||||
| [`todo/TASKS-2.md`](todo/TASKS-2.md) | **Subagent Orchestration (Container Model)** — SubagentContainer、Orchestrator、Presets enforcement、Subagent Plan Mode(TASKS-1 の型変更前提メモ追記済み) |
|
| [`todo/TASKS-2.md`](todo/TASKS-2.md) | **Subagent Orchestration (Container Model)** — SubagentContainer、Orchestrator、Presets enforcement、Subagent Plan Mode(TASKS-1 の型変更前提メモ追記済み) |
|
||||||
| [`todo/TASKS-3.md`](todo/TASKS-3.md) | ~~**Session DAG (SQLite Store)**~~ ✅ 実装済み(Phase 0–3: SQLite SessionStore、LegacyAdapter、Fork/Report、CompactOldTurns、`/session` CLI コマンド、Mini App グラフ UI) |
|
| [`todo/TASKS-3.md`](todo/TASKS-3.md) | ~~**Session DAG (SQLite Store)**~~ ✅ 実装済み(Phase 0–3: SQLite SessionStore、LegacyAdapter、Fork/Report、CompactOldTurns、`/session` CLI コマンド、Mini App グラフ UI) |
|
||||||
| [`todo/TASKS-4.md`](todo/TASKS-4.md) | **Mini App & Static Serving** — 静的配信の汎用化、バンドラ導入、フロントエンドテスト追加 |
|
| [`todo/TASKS-4.md`](todo/TASKS-4.md) | ~~**Mini App & Static Serving**~~ ✅ 実装済み(`http.FileServer` 統合、テンプレート注入、Bun ビルド導線、frontend unit test + CI `pnpm test`) |
|
||||||
| [`todo/TASKS-5.md`](todo/TASKS-5.md) | ~~**Heartbeat Worktree Management**~~ ✅ 実装済み(`/plan worktrees` の `list/inspect/merge/dispose`、安全化した `PruneOrphaned`、Mini App `/miniapp/api/worktrees` + Git タブ UI) |
|
| [`todo/TASKS-5.md`](todo/TASKS-5.md) | ~~**Heartbeat Worktree Management**~~ ✅ 実装済み(`/plan worktrees` の `list/inspect/merge/dispose`、安全化した `PruneOrphaned`、Mini App `/miniapp/api/worktrees` + Git タブ UI) |
|
||||||
| [`todo/TASKS-6.md`](todo/TASKS-6.md) | **SOUL.md — AI Persona Evolution** — 睡眠フェーズで体験を統合・忘却し人格を再構成。TASKS-2 完了後に着手 |
|
| [`todo/TASKS-6.md`](todo/TASKS-6.md) | **SOUL.md — AI Persona Evolution** — 睡眠フェーズで体験を統合・忘却し人格を再構成。TASKS-2 完了後に着手 |
|
||||||
| [`todo/TASKS-7.md`](todo/TASKS-7.md) | **Provider Wire Compatibility Hardening** — openai_compat の provider 別 wire 分岐(OpenAI strict / Gemini)、thought_signature round-trip 保全、互換テスト追加 |
|
| [`todo/TASKS-7.md`](todo/TASKS-7.md) | **Provider Wire Compatibility Hardening** — openai_compat の provider 別 wire 分岐(OpenAI strict / Gemini)、thought_signature round-trip 保全、互換テスト追加 |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
27
pkg/miniapp/frontend/build.mjs
Normal file
27
pkg/miniapp/frontend/build.mjs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { copyFile, mkdir, rm } from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const rootDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const srcDir = path.join(rootDir, 'src');
|
||||||
|
const outDir = path.join(rootDir, '..', 'static', 'dist');
|
||||||
|
|
||||||
|
await rm(outDir, { recursive: true, force: true });
|
||||||
|
await mkdir(outDir, { recursive: true });
|
||||||
|
|
||||||
|
const result = await Bun.build({
|
||||||
|
entrypoints: [path.join(srcDir, 'app.js')],
|
||||||
|
outdir: outDir,
|
||||||
|
target: 'browser',
|
||||||
|
format: 'iife',
|
||||||
|
sourcemap: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
for (const log of result.logs) {
|
||||||
|
console.error(log);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await copyFile(path.join(srcDir, 'map.js'), path.join(outDir, 'map.js'));
|
||||||
14
pkg/miniapp/frontend/package.json
Normal file
14
pkg/miniapp/frontend/package.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "miniapp-frontend",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun run ./build.mjs",
|
||||||
|
"test": "vitest run"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"happy-dom": "^16.8.1",
|
||||||
|
"vitest": "^2.1.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
900
pkg/miniapp/frontend/pnpm-lock.yaml
generated
Normal file
900
pkg/miniapp/frontend/pnpm-lock.yaml
generated
Normal file
|
|
@ -0,0 +1,900 @@
|
||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
happy-dom:
|
||||||
|
specifier: ^16.8.1
|
||||||
|
version: 16.8.1
|
||||||
|
vitest:
|
||||||
|
specifier: ^2.1.9
|
||||||
|
version: 2.1.9(happy-dom@16.8.1)
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.21.5':
|
||||||
|
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.21.5':
|
||||||
|
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.21.5':
|
||||||
|
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.21.5':
|
||||||
|
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.21.5':
|
||||||
|
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.21.5':
|
||||||
|
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.21.5':
|
||||||
|
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||||
|
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||||
|
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||||
|
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||||
|
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||||
|
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||||
|
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||||
|
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/estree@1.0.8':
|
||||||
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@vitest/expect@2.1.9':
|
||||||
|
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||||
|
|
||||||
|
'@vitest/mocker@2.1.9':
|
||||||
|
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^5.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/pretty-format@2.1.9':
|
||||||
|
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
|
||||||
|
|
||||||
|
'@vitest/runner@2.1.9':
|
||||||
|
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@2.1.9':
|
||||||
|
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
|
||||||
|
|
||||||
|
'@vitest/spy@2.1.9':
|
||||||
|
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
|
||||||
|
|
||||||
|
'@vitest/utils@2.1.9':
|
||||||
|
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
||||||
|
|
||||||
|
assertion-error@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
cac@6.7.14:
|
||||||
|
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
check-error@2.1.3:
|
||||||
|
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
deep-eql@5.0.2:
|
||||||
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0:
|
||||||
|
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||||
|
|
||||||
|
esbuild@0.21.5:
|
||||||
|
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
|
expect-type@1.3.0:
|
||||||
|
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
happy-dom@16.8.1:
|
||||||
|
resolution: {integrity: sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
loupe@3.2.1:
|
||||||
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
nanoid@3.3.11:
|
||||||
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
pathe@1.1.2:
|
||||||
|
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||||
|
|
||||||
|
pathval@2.0.1:
|
||||||
|
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||||
|
engines: {node: '>= 14.16'}
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
postcss@8.5.8:
|
||||||
|
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
rollup@4.59.0:
|
||||||
|
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
siginfo@2.0.0:
|
||||||
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
|
source-map-js@1.2.1:
|
||||||
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
|
std-env@3.10.0:
|
||||||
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
|
tinybench@2.9.0:
|
||||||
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
tinyexec@0.3.2:
|
||||||
|
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||||
|
|
||||||
|
tinypool@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
tinyrainbow@1.2.0:
|
||||||
|
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyspy@3.0.2:
|
||||||
|
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
vite-node@2.1.9:
|
||||||
|
resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
vite@5.4.21:
|
||||||
|
resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': ^18.0.0 || >=20.0.0
|
||||||
|
less: '*'
|
||||||
|
lightningcss: ^1.21.0
|
||||||
|
sass: '*'
|
||||||
|
sass-embedded: '*'
|
||||||
|
stylus: '*'
|
||||||
|
sugarss: '*'
|
||||||
|
terser: ^5.4.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
lightningcss:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
sass-embedded:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
terser:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
vitest@2.1.9:
|
||||||
|
resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/node': ^18.0.0 || >=20.0.0
|
||||||
|
'@vitest/browser': 2.1.9
|
||||||
|
'@vitest/ui': 2.1.9
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0:
|
||||||
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
whatwg-mimetype@3.0.0:
|
||||||
|
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.21.5':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@vitest/expect@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 2.1.9
|
||||||
|
'@vitest/utils': 2.1.9
|
||||||
|
chai: 5.3.3
|
||||||
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
|
'@vitest/mocker@2.1.9(vite@5.4.21)':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 2.1.9
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.21
|
||||||
|
optionalDependencies:
|
||||||
|
vite: 5.4.21
|
||||||
|
|
||||||
|
'@vitest/pretty-format@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
|
'@vitest/runner@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/utils': 2.1.9
|
||||||
|
pathe: 1.1.2
|
||||||
|
|
||||||
|
'@vitest/snapshot@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 2.1.9
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pathe: 1.1.2
|
||||||
|
|
||||||
|
'@vitest/spy@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
tinyspy: 3.0.2
|
||||||
|
|
||||||
|
'@vitest/utils@2.1.9':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 2.1.9
|
||||||
|
loupe: 3.2.1
|
||||||
|
tinyrainbow: 1.2.0
|
||||||
|
|
||||||
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
|
cac@6.7.14: {}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
dependencies:
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
check-error: 2.1.3
|
||||||
|
deep-eql: 5.0.2
|
||||||
|
loupe: 3.2.1
|
||||||
|
pathval: 2.0.1
|
||||||
|
|
||||||
|
check-error@2.1.3: {}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0: {}
|
||||||
|
|
||||||
|
esbuild@0.21.5:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.21.5
|
||||||
|
'@esbuild/android-arm': 0.21.5
|
||||||
|
'@esbuild/android-arm64': 0.21.5
|
||||||
|
'@esbuild/android-x64': 0.21.5
|
||||||
|
'@esbuild/darwin-arm64': 0.21.5
|
||||||
|
'@esbuild/darwin-x64': 0.21.5
|
||||||
|
'@esbuild/freebsd-arm64': 0.21.5
|
||||||
|
'@esbuild/freebsd-x64': 0.21.5
|
||||||
|
'@esbuild/linux-arm': 0.21.5
|
||||||
|
'@esbuild/linux-arm64': 0.21.5
|
||||||
|
'@esbuild/linux-ia32': 0.21.5
|
||||||
|
'@esbuild/linux-loong64': 0.21.5
|
||||||
|
'@esbuild/linux-mips64el': 0.21.5
|
||||||
|
'@esbuild/linux-ppc64': 0.21.5
|
||||||
|
'@esbuild/linux-riscv64': 0.21.5
|
||||||
|
'@esbuild/linux-s390x': 0.21.5
|
||||||
|
'@esbuild/linux-x64': 0.21.5
|
||||||
|
'@esbuild/netbsd-x64': 0.21.5
|
||||||
|
'@esbuild/openbsd-x64': 0.21.5
|
||||||
|
'@esbuild/sunos-x64': 0.21.5
|
||||||
|
'@esbuild/win32-arm64': 0.21.5
|
||||||
|
'@esbuild/win32-ia32': 0.21.5
|
||||||
|
'@esbuild/win32-x64': 0.21.5
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
|
||||||
|
expect-type@1.3.0: {}
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
happy-dom@16.8.1:
|
||||||
|
dependencies:
|
||||||
|
webidl-conversions: 7.0.0
|
||||||
|
whatwg-mimetype: 3.0.0
|
||||||
|
|
||||||
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
pathe@1.1.2: {}
|
||||||
|
|
||||||
|
pathval@2.0.1: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
postcss@8.5.8:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.11
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
rollup@4.59.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
optionalDependencies:
|
||||||
|
'@rollup/rollup-android-arm-eabi': 4.59.0
|
||||||
|
'@rollup/rollup-android-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-darwin-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-darwin-x64': 4.59.0
|
||||||
|
'@rollup/rollup-freebsd-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-freebsd-x64': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-arm64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-loong64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-loong64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-ppc64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-riscv64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-linux-s390x-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-x64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-linux-x64-musl': 4.59.0
|
||||||
|
'@rollup/rollup-openbsd-x64': 4.59.0
|
||||||
|
'@rollup/rollup-openharmony-arm64': 4.59.0
|
||||||
|
'@rollup/rollup-win32-arm64-msvc': 4.59.0
|
||||||
|
'@rollup/rollup-win32-ia32-msvc': 4.59.0
|
||||||
|
'@rollup/rollup-win32-x64-gnu': 4.59.0
|
||||||
|
'@rollup/rollup-win32-x64-msvc': 4.59.0
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
|
tinyexec@0.3.2: {}
|
||||||
|
|
||||||
|
tinypool@1.1.1: {}
|
||||||
|
|
||||||
|
tinyrainbow@1.2.0: {}
|
||||||
|
|
||||||
|
tinyspy@3.0.2: {}
|
||||||
|
|
||||||
|
vite-node@2.1.9:
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.3
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
|
pathe: 1.1.2
|
||||||
|
vite: 5.4.21
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
|
vite@5.4.21:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.21.5
|
||||||
|
postcss: 8.5.8
|
||||||
|
rollup: 4.59.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
vitest@2.1.9(happy-dom@16.8.1):
|
||||||
|
dependencies:
|
||||||
|
'@vitest/expect': 2.1.9
|
||||||
|
'@vitest/mocker': 2.1.9(vite@5.4.21)
|
||||||
|
'@vitest/pretty-format': 2.1.9
|
||||||
|
'@vitest/runner': 2.1.9
|
||||||
|
'@vitest/snapshot': 2.1.9
|
||||||
|
'@vitest/spy': 2.1.9
|
||||||
|
'@vitest/utils': 2.1.9
|
||||||
|
chai: 5.3.3
|
||||||
|
debug: 4.4.3
|
||||||
|
expect-type: 1.3.0
|
||||||
|
magic-string: 0.30.21
|
||||||
|
pathe: 1.1.2
|
||||||
|
std-env: 3.10.0
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinyexec: 0.3.2
|
||||||
|
tinypool: 1.1.1
|
||||||
|
tinyrainbow: 1.2.0
|
||||||
|
vite: 5.4.21
|
||||||
|
vite-node: 2.1.9
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
happy-dom: 16.8.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- msw
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
|
||||||
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
|
whatwg-mimetype@3.0.0: {}
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
siginfo: 2.0.0
|
||||||
|
stackback: 0.0.2
|
||||||
1341
pkg/miniapp/frontend/src/app.js
Normal file
1341
pkg/miniapp/frontend/src/app.js
Normal file
File diff suppressed because it is too large
Load diff
106
pkg/miniapp/frontend/src/logs_view.js
Normal file
106
pkg/miniapp/frontend/src/logs_view.js
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
const SAFE_LEVELS = new Set(['debug', 'info', 'warn', 'error']);
|
||||||
|
|
||||||
|
function stringifyFieldValue(value) {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
} catch {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function escapeHtml(value) {
|
||||||
|
return String(value == null ? '' : value)
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll("'", ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderFields(fields) {
|
||||||
|
if (!fields || typeof fields !== 'object' || Array.isArray(fields)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const keys = Object.keys(fields);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = keys.map((key) => `${key}=${stringifyFieldValue(fields[key])}`);
|
||||||
|
return ` <span class="log-fields">{${escapeHtml(parts.join(', '))}}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterLogs(entries, component = '') {
|
||||||
|
if (!Array.isArray(entries) || entries.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!component) {
|
||||||
|
return entries.slice();
|
||||||
|
}
|
||||||
|
return entries.filter((entry) => (entry?.component || '') === component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function paginateLogs(entries, page = 1, pageSize = 100) {
|
||||||
|
const list = Array.isArray(entries) ? entries : [];
|
||||||
|
const size = Math.max(1, Number(pageSize) || 100);
|
||||||
|
const totalPages = Math.max(1, Math.ceil(list.length / size));
|
||||||
|
const currentPage = Math.min(Math.max(1, Number(page) || 1), totalPages);
|
||||||
|
|
||||||
|
// Page 1 is the newest page (tail of the list).
|
||||||
|
const end = list.length - (currentPage - 1) * size;
|
||||||
|
const start = Math.max(0, end - size);
|
||||||
|
return {
|
||||||
|
items: list.slice(start, Math.max(start, end)),
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
pageSize: size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderLogs(entries, options = {}) {
|
||||||
|
const component = options.component || '';
|
||||||
|
const filtered = filterLogs(entries, component);
|
||||||
|
const paged = paginateLogs(filtered, options.page, options.pageSize);
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
for (const entry of paged.items) {
|
||||||
|
const levelRaw = String(entry?.level || 'info').toLowerCase();
|
||||||
|
const level = SAFE_LEVELS.has(levelRaw) ? levelRaw : 'info';
|
||||||
|
const ts = entry?.timestamp ? String(entry.timestamp).substring(11, 19) : '';
|
||||||
|
const componentHTML = entry?.component
|
||||||
|
? `<span class="log-comp">${escapeHtml(entry.component)}</span>`
|
||||||
|
: '';
|
||||||
|
const fieldsHTML = renderFields(entry?.fields);
|
||||||
|
const message = escapeHtml(entry?.message || '');
|
||||||
|
|
||||||
|
html += '<div class="log-entry">' +
|
||||||
|
`<span class="log-ts">${ts}</span>` +
|
||||||
|
`<span class="log-badge ${level}">${level}</span>` +
|
||||||
|
componentHTML +
|
||||||
|
`<span class="log-msg">${message}${fieldsHTML}</span>` +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
totalItems: filtered.length,
|
||||||
|
currentPage: paged.currentPage,
|
||||||
|
totalPages: paged.totalPages,
|
||||||
|
pageSize: paged.pageSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderLogsInto(container, entries, options = {}) {
|
||||||
|
const view = renderLogs(entries, options);
|
||||||
|
container.innerHTML = view.html;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
70
pkg/miniapp/frontend/src/logs_view.test.js
Normal file
70
pkg/miniapp/frontend/src/logs_view.test.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { escapeHtml, filterLogs, paginateLogs, renderFields, renderLogs, renderLogsInto } from './logs_view.js';
|
||||||
|
|
||||||
|
describe('logs_view', () => {
|
||||||
|
it('renders a normal log message row', () => {
|
||||||
|
const view = renderLogs([
|
||||||
|
{
|
||||||
|
timestamp: '2026-03-05T12:34:56Z',
|
||||||
|
level: 'INFO',
|
||||||
|
component: 'telego',
|
||||||
|
message: 'connected',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(view.totalItems).toBe(1);
|
||||||
|
expect(view.html).toContain('12:34:56');
|
||||||
|
expect(view.html).toContain('log-badge info');
|
||||||
|
expect(view.html).toContain('connected');
|
||||||
|
expect(view.html).toContain('telego');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders fields for empty/single/multiple cases', () => {
|
||||||
|
expect(renderFields(null)).toBe('');
|
||||||
|
expect(renderFields({})).toBe('');
|
||||||
|
expect(renderFields({ req_id: 42 })).toContain('{req_id=42}');
|
||||||
|
|
||||||
|
const multi = renderFields({ a: 'x', b: 2 });
|
||||||
|
expect(multi).toContain('a=x');
|
||||||
|
expect(multi).toContain('b=2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sanitizes potentially dangerous HTML', () => {
|
||||||
|
const xss = '<img src=x onerror=alert(1) />';
|
||||||
|
const escaped = escapeHtml(xss);
|
||||||
|
expect(escaped).not.toContain('<img');
|
||||||
|
expect(escaped).toContain('<img');
|
||||||
|
|
||||||
|
const view = renderLogs([{ message: xss, level: 'warn' }]);
|
||||||
|
const container = document.createElement('div');
|
||||||
|
renderLogsInto(container, [{ message: xss, level: 'warn' }]);
|
||||||
|
expect(container.querySelector('img')).toBeNull();
|
||||||
|
expect(view.html).toContain('<img');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports filtering and pagination', () => {
|
||||||
|
const entries = [];
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
entries.push({
|
||||||
|
level: 'debug',
|
||||||
|
component: i % 2 === 0 ? 'telego' : 'dev-console',
|
||||||
|
message: `entry-${i}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filtered = filterLogs(entries, 'telego');
|
||||||
|
expect(filtered.length).toBe(13);
|
||||||
|
|
||||||
|
const pageInfo = paginateLogs(filtered, 2, 5);
|
||||||
|
expect(pageInfo.totalPages).toBe(3);
|
||||||
|
expect(pageInfo.items.length).toBe(5);
|
||||||
|
|
||||||
|
const page1 = renderLogs(entries, { component: 'telego', page: 1, pageSize: 5 });
|
||||||
|
const page2 = renderLogs(entries, { component: 'telego', page: 2, pageSize: 5 });
|
||||||
|
expect(page1.totalPages).toBe(3);
|
||||||
|
expect(page1.html).not.toBe(page2.html);
|
||||||
|
expect(page1.html).toContain('entry-24');
|
||||||
|
expect(page2.html).toContain('entry-14');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -203,3 +203,10 @@ function _drawMapFallback(ctx) {
|
||||||
_r(ctx, '#4a2408', 162, 284, 12, 16);
|
_r(ctx, '#4a2408', 162, 284, 12, 16);
|
||||||
_r(ctx, _C.doorGold, 170, 291, 5, 5); // handle
|
_r(ctx, _C.doorGold, 170, 291, 5, 5); // handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose map helpers for app.js runtime.
|
||||||
|
globalThis.MAP_POSITIONS = MAP_POSITIONS;
|
||||||
|
globalThis.loadMapAsset = loadMapAsset;
|
||||||
|
globalThis.drawMap = drawMap;
|
||||||
|
|
||||||
|
|
||||||
912
pkg/miniapp/frontend/src/styles.css
Normal file
912
pkg/miniapp/frontend/src/styles.css
Normal file
|
|
@ -0,0 +1,912 @@
|
||||||
|
:root {
|
||||||
|
--bg: var(--tg-theme-bg-color, #ffffff);
|
||||||
|
--text: var(--tg-theme-text-color, #000000);
|
||||||
|
--hint: var(--tg-theme-hint-color, #999999);
|
||||||
|
--link: var(--tg-theme-link-color, #2481cc);
|
||||||
|
--btn: var(--tg-theme-button-color, #2481cc);
|
||||||
|
--btn-text: var(--tg-theme-button-text-color, #ffffff);
|
||||||
|
--secondary-bg: var(--tg-theme-secondary-bg-color, #f0f0f0);
|
||||||
|
--done: #34c759;
|
||||||
|
--current: var(--btn);
|
||||||
|
--pending-phase: var(--hint);
|
||||||
|
/* Liquid Glass */
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.55);
|
||||||
|
--glass-border: rgba(0, 0, 0, 0.08);
|
||||||
|
--glass-border-interactive: rgba(0, 0, 0, 0.15);
|
||||||
|
--glass-shadow: rgba(0, 0, 0, 0.06);
|
||||||
|
--glass-divider: rgba(0, 0, 0, 0.06);
|
||||||
|
--tab-bar-bg: rgba(255, 255, 255, 0.72);
|
||||||
|
--tab-track-bg: rgba(0, 0, 0, 0.06);
|
||||||
|
--tab-pill-bg: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: var(--tg-theme-bg-color, #1c1c1e);
|
||||||
|
--text: var(--tg-theme-text-color, #ffffff);
|
||||||
|
--hint: var(--tg-theme-hint-color, #8e8e93);
|
||||||
|
--link: var(--tg-theme-link-color, #5ac8fa);
|
||||||
|
--btn: var(--tg-theme-button-color, #5ac8fa);
|
||||||
|
--btn-text: var(--tg-theme-button-text-color, #ffffff);
|
||||||
|
--secondary-bg: var(--tg-theme-secondary-bg-color, #2c2c2e);
|
||||||
|
/* Liquid Glass — dark */
|
||||||
|
--glass-bg: rgba(255, 255, 255, 0.08);
|
||||||
|
--glass-border: rgba(255, 255, 255, 0.1);
|
||||||
|
--glass-border-interactive: rgba(255, 255, 255, 0.22);
|
||||||
|
--glass-shadow: rgba(0, 0, 0, 0.2);
|
||||||
|
--glass-divider: rgba(255, 255, 255, 0.08);
|
||||||
|
--tab-bar-bg: rgba(28, 28, 30, 0.72);
|
||||||
|
--tab-track-bg: rgba(255, 255, 255, 0.1);
|
||||||
|
--tab-pill-bg: rgba(255, 255, 255, 0.16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--tab-bar-bg);
|
||||||
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-inner {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--tab-track-bg);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: calc(100% / var(--tab-count, 7) - 2px);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--tab-pill-bg);
|
||||||
|
box-shadow: 0 0.5px 2px rgba(0,0,0,0.12), 0 0.5px 1px rgba(0,0,0,0.08);
|
||||||
|
transition: transform 0.38s cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
flex: 1;
|
||||||
|
padding: 7px 4px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--hint);
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.25s ease;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
|
.glass {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 1px 3px var(--glass-shadow);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
.glass-interactive { border-color: var(--glass-border-interactive); }
|
||||||
|
|
||||||
|
.panel { display: none; padding: 16px; }
|
||||||
|
.panel.active { display: block; }
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--hint);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.6px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-value {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plan - phase/step list */
|
||||||
|
.phase {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-indicator {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-indicator.done { background: var(--done); }
|
||||||
|
.phase-indicator.current { background: var(--current); }
|
||||||
|
.phase-indicator.pending { background: var(--glass-divider); color: var(--hint); }
|
||||||
|
|
||||||
|
.phase-title { flex: 1; }
|
||||||
|
.phase-progress {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--hint);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 14px 12px 34px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step:active { transform: scale(0.97); }
|
||||||
|
.step:not(.step-done) {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border-interactive);
|
||||||
|
box-shadow: 0 0.5px 2px var(--glass-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-check {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--hint);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-check.done {
|
||||||
|
background: var(--done);
|
||||||
|
border-color: var(--done);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-check.done::after {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid #fff;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-text.done {
|
||||||
|
color: var(--hint);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skills */
|
||||||
|
.skill-item {
|
||||||
|
padding: 14px 14px 14px 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s, border-color 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
.skill-item.selected {
|
||||||
|
border-color: var(--btn);
|
||||||
|
box-shadow: 0 2px 8px var(--glass-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-body { flex: 1; min-width: 0; }
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--hint);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-source {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: var(--tab-track-bg);
|
||||||
|
color: var(--hint);
|
||||||
|
margin-top: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-arrow {
|
||||||
|
color: var(--hint);
|
||||||
|
font-size: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.skill-item.selected .skill-arrow { color: var(--btn); }
|
||||||
|
|
||||||
|
/* Stats */
|
||||||
|
.stat-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 11px 0;
|
||||||
|
border-bottom: 1px solid var(--glass-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-row:last-child { border-bottom: none; }
|
||||||
|
.stat-label { color: var(--hint); font-size: 14px; }
|
||||||
|
.stat-value { font-weight: 600; font-size: 14px; }
|
||||||
|
|
||||||
|
/* Send bar */
|
||||||
|
.send-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--tab-bar-bg);
|
||||||
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
backdrop-filter: saturate(180%) blur(20px);
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
border-top: 1px solid var(--glass-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-input:focus { border-color: var(--btn); }
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: none;
|
||||||
|
background: var(--btn);
|
||||||
|
color: var(--btn-text);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), background 0.15s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:active { transform: scale(0.95); }
|
||||||
|
.send-btn:disabled { opacity: 0.5; }
|
||||||
|
.send-btn.sent { background: var(--done); }
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--hint);
|
||||||
|
padding: 48px 20px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--hint);
|
||||||
|
padding: 48px 20px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-tiles {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-tile {
|
||||||
|
padding: 16px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), background 0.15s;
|
||||||
|
text-align: center;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-tile:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmd-tile.sent {
|
||||||
|
background: var(--btn);
|
||||||
|
color: var(--btn-text);
|
||||||
|
border-color: var(--btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MEMORY.md raw display */
|
||||||
|
.memory-view {
|
||||||
|
font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 16px 0 8px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
.memory-view .md-h1:first-child { margin-top: 0; }
|
||||||
|
|
||||||
|
.memory-view .md-h2 {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 14px 0 6px;
|
||||||
|
color: var(--link);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 12px 0 4px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-checkbox {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-checkbox-icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1.5px solid var(--hint);
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-checkbox-icon.checked {
|
||||||
|
background: var(--done);
|
||||||
|
border-color: var(--done);
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-checkbox-icon.checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 1px;
|
||||||
|
width: 4px;
|
||||||
|
height: 8px;
|
||||||
|
border: solid #fff;
|
||||||
|
border-width: 0 1.5px 1.5px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-quote {
|
||||||
|
border-left: 3px solid var(--hint);
|
||||||
|
padding-left: 10px;
|
||||||
|
color: var(--hint);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-bullet {
|
||||||
|
margin: 2px 0;
|
||||||
|
padding-left: 12px;
|
||||||
|
text-indent: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-view .md-bullet::before {
|
||||||
|
content: '\2022 ';
|
||||||
|
color: var(--hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide to Approve */
|
||||||
|
.slide-approve-wrap {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-track {
|
||||||
|
position: relative;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
touch-action: none;
|
||||||
|
border: 1.5px solid;
|
||||||
|
border-image: linear-gradient(135deg, var(--btn), var(--glass-border)) 1;
|
||||||
|
border-image: none;
|
||||||
|
border-color: var(--btn);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.08);
|
||||||
|
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-track.approved {
|
||||||
|
background: var(--done);
|
||||||
|
border-color: var(--done);
|
||||||
|
box-shadow: 0 0 16px rgba(76,175,80,0.4), 0 2px 8px rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--btn);
|
||||||
|
color: var(--btn-text);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: grab;
|
||||||
|
transition: left 0.3s cubic-bezier(0.25, 1, 0.5, 1);
|
||||||
|
z-index: 1;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-thumb svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-thumb.dragging {
|
||||||
|
transition: none;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0%, 100% { opacity: 0.7; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
transition: color 0.3s;
|
||||||
|
animation: shimmer 2.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-approve-track.approved .slide-approve-label {
|
||||||
|
color: #fff;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Git log */
|
||||||
|
.git-commit {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid var(--glass-border);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.git-commit:last-child { border-bottom: none; }
|
||||||
|
.git-hash {
|
||||||
|
font-family: monospace;
|
||||||
|
color: var(--btn);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.git-subject { flex: 1; color: var(--text); }
|
||||||
|
.git-meta { color: var(--hint); font-size: 11px; flex-shrink: 0; text-align: right; }
|
||||||
|
.git-status {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.git-status-m { color: #e2b93d; }
|
||||||
|
.git-status-a { color: #4caf50; }
|
||||||
|
.git-status-d { color: #ef5350; }
|
||||||
|
.git-status-u { color: var(--hint); }
|
||||||
|
|
||||||
|
.git-repo-item {
|
||||||
|
padding: 14px 14px 14px 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.git-repo-item:active { transform: scale(0.98); }
|
||||||
|
.git-repo-body { flex: 1; min-width: 0; }
|
||||||
|
.git-repo-name { font-weight: 600; font-size: 15px; margin-bottom: 4px; }
|
||||||
|
.git-repo-branch {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--hint);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.git-repo-arrow {
|
||||||
|
color: var(--hint);
|
||||||
|
font-size: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.git-back-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--btn);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.git-back-btn:active { opacity: 0.6; }
|
||||||
|
|
||||||
|
.worktree-list {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.worktree-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
background: var(--glass-bg);
|
||||||
|
}
|
||||||
|
.worktree-item.dirty {
|
||||||
|
border-color: rgba(255, 152, 0, 0.45);
|
||||||
|
}
|
||||||
|
.worktree-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.worktree-name-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.worktree-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.worktree-branch {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--hint);
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
.worktree-last {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--hint);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.worktree-dirty,
|
||||||
|
.worktree-clean {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
.worktree-dirty {
|
||||||
|
color: #c26b00;
|
||||||
|
background: rgba(255, 152, 0, 0.2);
|
||||||
|
}
|
||||||
|
.worktree-clean {
|
||||||
|
color: #1b8f3a;
|
||||||
|
background: rgba(76, 175, 80, 0.18);
|
||||||
|
}
|
||||||
|
.worktree-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.worktree-btn {
|
||||||
|
border: 1px solid var(--glass-border-interactive);
|
||||||
|
background: var(--glass-bg);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 74px;
|
||||||
|
}
|
||||||
|
.worktree-btn.merge { color: var(--btn); }
|
||||||
|
.worktree-btn.dispose { color: #d14b4b; }
|
||||||
|
.worktree-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
/* Dev header */
|
||||||
|
.dev-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.dev-header-title { font-weight: 600; font-size: 15px; }
|
||||||
|
.dev-header-target {
|
||||||
|
color: var(--hint);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dev target cards */
|
||||||
|
.dev-target-item {
|
||||||
|
padding: 12px 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.2s, border-color 0.2s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.dev-target-item:active { transform: scale(0.98); }
|
||||||
|
.dev-target-item.active {
|
||||||
|
border-color: var(--btn);
|
||||||
|
box-shadow: 0 2px 8px var(--glass-shadow);
|
||||||
|
}
|
||||||
|
.dev-target-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: var(--hint);
|
||||||
|
}
|
||||||
|
.dev-target-dot.on { background: var(--done); }
|
||||||
|
.dev-target-name { font-weight: 600; font-size: 14px; }
|
||||||
|
.dev-target-url {
|
||||||
|
color: var(--hint);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.dev-target-delete {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 6px;
|
||||||
|
margin: -6px;
|
||||||
|
margin-left: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--hint);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: color 0.15s, background 0.15s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.dev-target-delete:active {
|
||||||
|
color: #ff3b30;
|
||||||
|
background: rgba(255, 59, 48, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log viewer */
|
||||||
|
.log-filter-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
|
||||||
|
.log-filter-chip {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid var(--glass-border-interactive);
|
||||||
|
background: var(--glass-bg);
|
||||||
|
color: var(--hint);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.log-filter-chip.active {
|
||||||
|
background: var(--btn);
|
||||||
|
color: var(--btn-text);
|
||||||
|
border-color: var(--btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
#logs-content {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
.log-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--hint);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.log-page-btn {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid var(--glass-border-interactive);
|
||||||
|
background: var(--glass-bg);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.log-page-btn:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.log-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 3px 0;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-bottom: 1px solid var(--glass-divider);
|
||||||
|
}
|
||||||
|
.log-ts { color: var(--hint); flex-shrink: 0; white-space: nowrap; }
|
||||||
|
.log-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
.log-badge.error { background: rgba(255,59,48,0.15); color: #ff3b30; }
|
||||||
|
.log-badge.warn { background: rgba(255,204,0,0.15); color: #cc9900; }
|
||||||
|
.log-badge.info { background: rgba(52,199,89,0.12); color: #34c759; }
|
||||||
|
.log-badge.debug { background: rgba(142,142,147,0.12); color: #8e8e93; }
|
||||||
|
.log-comp { color: var(--link); flex-shrink: 0; font-size: 10px; }
|
||||||
|
.log-msg { flex: 1; word-break: break-all; color: var(--text); }
|
||||||
|
.log-fields { color: var(--hint); font-size: 10px; }
|
||||||
|
.log-actions { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
|
||||||
|
.log-snap-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border-interactive);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Orch panel ── */
|
||||||
|
.orch-room-row { display:flex; align-items:flex-start; padding:12px 0; }
|
||||||
|
.orch-side { display:flex; flex-direction:column; align-items:center; gap:12px; padding-top:20px; width:40px; flex-shrink:0; }
|
||||||
|
.orch-badge { display:flex; flex-direction:column; align-items:center; gap:3px; opacity:0.3; transition:opacity 0.3s; }
|
||||||
|
.orch-badge.alive { opacity:1; }
|
||||||
|
.orch-badge.alive .orch-badge-label { color:#4a6ac0; }
|
||||||
|
.orch-badge.alive .orch-badge-dot { background:#4ade80; }
|
||||||
|
.orch-badge.toolcall .orch-badge-dot { background:#fb923c; animation:orch-blink 0.2s step-end infinite; }
|
||||||
|
.orch-badge.waiting .orch-badge-dot { background:#60a5fa; }
|
||||||
|
.orch-badge.talking .orch-badge-label{ color:#facc15; }
|
||||||
|
.orch-badge.talking .orch-badge-dot { background:#facc15; animation:orch-blink 0.6s step-end infinite; }
|
||||||
|
.orch-badge-emoji { font-size:16px; line-height:1; }
|
||||||
|
.orch-badge-label { font-size:7px; color:var(--hint); letter-spacing:0.05em; text-transform:uppercase; }
|
||||||
|
.orch-badge-dot { width:4px; height:4px; border-radius:50%; background:var(--hint); }
|
||||||
|
@keyframes orch-blink { 50% { opacity:0; } }
|
||||||
|
.orch-canvas-wrap { flex:1; min-width:0; border-radius:12px; overflow:hidden; background:#060810; }
|
||||||
|
.orch-canvas-wrap canvas { image-rendering:pixelated; image-rendering:crisp-edges; display:block; width:100%; aspect-ratio:1/1; }
|
||||||
|
.orch-status { text-align:center; font-size:11px; color:var(--hint); padding:6px 0 4px; }
|
||||||
|
.orch-dot { display:inline-block; width:6px; height:6px; border-radius:50%; background:var(--hint); margin-right:4px; vertical-align:middle; }
|
||||||
|
.orch-dot.on { background:#4ade80; }
|
||||||
|
|
||||||
|
/* Session graph tree */
|
||||||
|
.session-tree { padding:0; margin:0; list-style:none; }
|
||||||
|
.session-tree-node { position:relative; padding:4px 0 4px 20px; font-size:13px; }
|
||||||
|
.session-tree-node::before {
|
||||||
|
content:''; position:absolute; left:0; top:0; bottom:0;
|
||||||
|
border-left:1px solid var(--secondary-bg);
|
||||||
|
}
|
||||||
|
.session-tree-node::after {
|
||||||
|
content:''; position:absolute; left:0; top:14px; width:16px;
|
||||||
|
border-top:1px solid var(--secondary-bg);
|
||||||
|
}
|
||||||
|
.session-tree-node:last-child::before { height:14px; }
|
||||||
|
.session-tree-children { padding-left:20px; margin:0; list-style:none; }
|
||||||
|
.session-tree-label { font-weight:600; }
|
||||||
|
.session-tree-meta { color:var(--hint); font-size:11px; margin-left:6px; }
|
||||||
|
.session-tree-icon { font-size:10px; margin-right:4px; }
|
||||||
|
.session-tree-icon.active { color:var(--done); }
|
||||||
|
.session-tree-icon.completed { color:var(--hint); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
8
pkg/miniapp/frontend/vitest.config.mjs
Normal file
8
pkg/miniapp/frontend/vitest.config.mjs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
include: ['src/**/*.test.js'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -2,18 +2,33 @@ package miniapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/orch"
|
"github.com/sipeed/picoclaw/pkg/orch"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed static/index.html static/map.js
|
//go:generate bun run --cwd frontend build
|
||||||
|
//go:embed static
|
||||||
var staticFS embed.FS
|
var staticFS embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
miniappStaticFS = mustMiniappStaticFS()
|
||||||
|
miniappTemplate = template.Must(template.ParseFS(staticFS, "static/index.html"))
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustMiniappStaticFS() fs.FS {
|
||||||
|
sub, err := fs.Sub(staticFS, "static")
|
||||||
|
if err != nil {
|
||||||
|
panic("miniapp: failed to create static sub filesystem: " + err.Error())
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
// Handler serves the Mini App HTML and API endpoints.
|
// Handler serves the Mini App HTML and API endpoints.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
provider DataProvider
|
provider DataProvider
|
||||||
|
|
@ -68,6 +83,8 @@ func (h *Handler) SetOrchBroadcaster(b *orch.Broadcaster) {
|
||||||
// RegisterRoutes registers Mini App routes on the given mux.
|
// RegisterRoutes registers Mini App routes on the given mux.
|
||||||
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/miniapp", h.serveIndex)
|
mux.HandleFunc("/miniapp", h.serveIndex)
|
||||||
|
mux.HandleFunc("/miniapp/index.html", h.serveIndex)
|
||||||
|
mux.HandleFunc("/miniapp/", h.serveStatic)
|
||||||
mux.HandleFunc("/miniapp/api/skills", h.requireAuth(h.apiSkills))
|
mux.HandleFunc("/miniapp/api/skills", h.requireAuth(h.apiSkills))
|
||||||
mux.HandleFunc("/miniapp/api/plan", h.requireAuth(h.apiPlan))
|
mux.HandleFunc("/miniapp/api/plan", h.requireAuth(h.apiPlan))
|
||||||
mux.HandleFunc("/miniapp/api/session", h.requireAuth(h.apiSession))
|
mux.HandleFunc("/miniapp/api/session", h.requireAuth(h.apiSession))
|
||||||
|
|
@ -84,30 +101,27 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||||
mux.HandleFunc("/miniapp/api/logs/snapshot", h.requireAuth(h.apiLogsSnapshot))
|
mux.HandleFunc("/miniapp/api/logs/snapshot", h.requireAuth(h.apiLogsSnapshot))
|
||||||
mux.HandleFunc("/miniapp/api/logs/snapshot/", h.requireAuth(h.apiLogsSnapshotDownload))
|
mux.HandleFunc("/miniapp/api/logs/snapshot/", h.requireAuth(h.apiLogsSnapshotDownload))
|
||||||
mux.HandleFunc("/miniapp/api/orchestration/ws", h.requireAuth(h.wsOrchestration))
|
mux.HandleFunc("/miniapp/api/orchestration/ws", h.requireAuth(h.wsOrchestration))
|
||||||
mux.HandleFunc("/miniapp/map.js", h.serveMapJS)
|
|
||||||
mux.HandleFunc("/miniapp/dev/console", h.apiDevConsole)
|
mux.HandleFunc("/miniapp/dev/console", h.apiDevConsole)
|
||||||
mux.HandleFunc("/miniapp/dev/", h.serveDevProxy)
|
mux.HandleFunc("/miniapp/dev/", h.serveDevProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) serveIndex(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := staticFS.ReadFile("static/index.html")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
if err != nil {
|
data := struct {
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
OrchEnabled bool
|
||||||
|
}{
|
||||||
|
OrchEnabled: h.orchBroadcaster != nil,
|
||||||
|
}
|
||||||
|
if err := miniappTemplate.Execute(w, data); err != nil {
|
||||||
|
http.Error(w, "failed to render template", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
if h.orchBroadcaster != nil {
|
|
||||||
data = []byte(strings.Replace(string(data), "</head>", "<script>var ORCH_ENABLED=true;</script></head>", 1))
|
|
||||||
}
|
|
||||||
w.Write(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) serveMapJS(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveStatic(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := staticFS.ReadFile("static/map.js")
|
if r.URL.Path == "/miniapp/" {
|
||||||
if err != nil {
|
h.serveIndex(w, r)
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
|
http.StripPrefix("/miniapp/", http.FileServer(http.FS(miniappStaticFS))).ServeHTTP(w, r)
|
||||||
w.Write(data)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gitpkg "github.com/sipeed/picoclaw/pkg/git"
|
gitpkg "github.com/sipeed/picoclaw/pkg/git"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/orch"
|
||||||
"github.com/sipeed/picoclaw/pkg/skills"
|
"github.com/sipeed/picoclaw/pkg/skills"
|
||||||
"github.com/sipeed/picoclaw/pkg/stats"
|
"github.com/sipeed/picoclaw/pkg/stats"
|
||||||
)
|
)
|
||||||
|
|
@ -227,6 +228,60 @@ func testInitData() string {
|
||||||
}, testBotToken)
|
}, testBotToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMiniApp_IndexTemplateInjectsOrchFlag(t *testing.T) {
|
||||||
|
notifier := NewStateNotifier()
|
||||||
|
h := NewHandler(&mockDataProvider{}, &mockSender{}, testBotToken, notifier, nil, "")
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w, httptest.NewRequest("GET", "/miniapp", nil))
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(w.Body.String(), "window.ORCH_ENABLED = false;") {
|
||||||
|
t.Fatalf("expected ORCH_ENABLED=false in rendered template")
|
||||||
|
}
|
||||||
|
|
||||||
|
h.SetOrchBroadcaster(orch.NewBroadcaster())
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w2, httptest.NewRequest("GET", "/miniapp", nil))
|
||||||
|
if w2.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200 with broadcaster, got %d", w2.Code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(w2.Body.String(), "window.ORCH_ENABLED = true;") {
|
||||||
|
t.Fatalf("expected ORCH_ENABLED=true in rendered template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiniApp_StaticFileServerServesAssets(t *testing.T) {
|
||||||
|
notifier := NewStateNotifier()
|
||||||
|
h := NewHandler(&mockDataProvider{}, &mockSender{}, testBotToken, notifier, nil, "")
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{path: "/miniapp/map-preview.html", want: "Orchestration Room"},
|
||||||
|
{path: "/miniapp/dist/map.js", want: "MAP_POSITIONS"},
|
||||||
|
{path: "/miniapp/dist/app.js", want: "renderLogs"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.path, func(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(w, httptest.NewRequest("GET", tc.path, nil))
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected 200 for %s, got %d", tc.path, w.Code)
|
||||||
|
}
|
||||||
|
if !strings.Contains(w.Body.String(), tc.want) {
|
||||||
|
t.Fatalf("expected %q in %s", tc.want, tc.path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestSSE_AuthRequired(t *testing.T) {
|
func TestSSE_AuthRequired(t *testing.T) {
|
||||||
notifier := NewStateNotifier()
|
notifier := NewStateNotifier()
|
||||||
h := NewHandler(&mockDataProvider{}, &mockSender{}, testBotToken, notifier, nil, "")
|
h := NewHandler(&mockDataProvider{}, &mockSender{}, testBotToken, notifier, nil, "")
|
||||||
|
|
|
||||||
1230
pkg/miniapp/static/dist/app.css
vendored
Normal file
1230
pkg/miniapp/static/dist/app.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1459
pkg/miniapp/static/dist/app.js
vendored
Normal file
1459
pkg/miniapp/static/dist/app.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
212
pkg/miniapp/static/dist/map.js
vendored
Normal file
212
pkg/miniapp/static/dist/map.js
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
// map.js — Orchestration Room
|
||||||
|
//
|
||||||
|
// External asset: drop map.png (320×320px) next to index.html to replace
|
||||||
|
// the procedural fallback. Character positions (MAP_POSITIONS) are defined
|
||||||
|
// in canvas-pixel coordinates and remain valid regardless of which rendering
|
||||||
|
// path is used — just make sure your map.png matches them.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// loadMapAsset(function() { drawMap(ctx); }); // call once on init
|
||||||
|
// drawMap(ctx); // call each frame
|
||||||
|
|
||||||
|
// ─── Character home positions (px, canvas 320×320) ─────────────────────────
|
||||||
|
//
|
||||||
|
// ┌──────────────────────────────┐
|
||||||
|
// │ [conductor desk] │ y ≈ 20–50
|
||||||
|
// │ 👑(160,58) 👩💼(108,58) │
|
||||||
|
// │ [carpet] │
|
||||||
|
// │ [WS1] [WS2] [WS3] │ y ≈ 80–100
|
||||||
|
// │ 🔍40 💻144 📊248 │ y = 106
|
||||||
|
// │ [meeting area] │ y ≈ 130–192
|
||||||
|
// │ [WS4] [WS5] │ y ≈ 200–220
|
||||||
|
// │ 🔧40 🎯144 │ y = 222
|
||||||
|
// │ 🚪(160,308) │ door
|
||||||
|
// └──────────────────────────────┘
|
||||||
|
|
||||||
|
var MAP_POSITIONS = {
|
||||||
|
door: { x: 160, y: 314 }, // entry / exit point
|
||||||
|
conductor: { x: 160, y: 58 },
|
||||||
|
secretary: { x: 108, y: 58 },
|
||||||
|
heartbeat: { x: 230, y: 58 }, // pigeon messenger — periodic heartbeat agent
|
||||||
|
meeting: { x: 160, y: 161 }, // neutral zone for conversations
|
||||||
|
stations: [
|
||||||
|
{ x: 40, y: 106 }, // S0 scout
|
||||||
|
{ x: 144, y: 106 }, // S1 analyst
|
||||||
|
{ x: 248, y: 106 }, // S2 coder
|
||||||
|
{ x: 40, y: 222 }, // S3 worker
|
||||||
|
{ x: 144, y: 222 }, // S4 coordinator
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Asset loading ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
var _mapImage = null;
|
||||||
|
|
||||||
|
// Call once before first draw. cb() is invoked when ready (image or fallback).
|
||||||
|
function loadMapAsset(cb) {
|
||||||
|
var img = new Image();
|
||||||
|
img.onload = function() { _mapImage = img; cb(); };
|
||||||
|
img.onerror = function() { cb(); }; // no map.png → use fallback
|
||||||
|
img.src = './map.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Public draw entry point ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function drawMap(ctx) {
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
if (_mapImage) {
|
||||||
|
ctx.drawImage(_mapImage, 0, 0, 320, 320);
|
||||||
|
} else {
|
||||||
|
_drawMapFallback(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Procedural fallback ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
var _C = {
|
||||||
|
wallDark: '#0c1018',
|
||||||
|
wallHighlight: '#252d3f',
|
||||||
|
floorA: '#171b2c',
|
||||||
|
floorB: '#1b2033',
|
||||||
|
carpetBase: '#1a2050',
|
||||||
|
carpetBorder: '#2a3480',
|
||||||
|
deskBack: '#2c3e6b',
|
||||||
|
deskTop: '#3a50a0',
|
||||||
|
deskEdge: '#4a6ac0',
|
||||||
|
deskShadow: '#1a2448',
|
||||||
|
monitorFrame: '#070b14',
|
||||||
|
monitorBlue: '#1040a0',
|
||||||
|
monitorGlow: '#4488ff',
|
||||||
|
wsBase: '#162818',
|
||||||
|
wsTop: '#1e3822',
|
||||||
|
wsEdge: '#2a5030',
|
||||||
|
termGlow: '#00dd55',
|
||||||
|
rugFill: '#1c2248',
|
||||||
|
rugBorder: '#283070',
|
||||||
|
doorMid: '#8a5818',
|
||||||
|
doorLight: '#a06820',
|
||||||
|
doorGold: '#c8940a',
|
||||||
|
};
|
||||||
|
|
||||||
|
function _r(ctx, color, x, y, w, h, alpha) {
|
||||||
|
ctx.globalAlpha = alpha === undefined ? 1 : alpha;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(x, y, w, h);
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _b(ctx, color, x, y, w, h) {
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dot(ctx, color, x, y) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(x, y, 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _workstation(ctx, x, y) {
|
||||||
|
_r(ctx, _C.wsBase, x, y, 48, 20);
|
||||||
|
_r(ctx, _C.wsTop, x, y, 48, 8);
|
||||||
|
_r(ctx, _C.wsEdge, x, y, 2, 20);
|
||||||
|
_r(ctx, _C.wsEdge, x+46, y, 2, 20);
|
||||||
|
_r(ctx, _C.wsEdge, x, y, 48, 2);
|
||||||
|
// terminal screen
|
||||||
|
_r(ctx, _C.monitorFrame, x+16, y+2, 16, 12);
|
||||||
|
_r(ctx, '#041008', x+17, y+3, 14, 10);
|
||||||
|
_r(ctx, '#003315', x+18, y+4, 12, 8);
|
||||||
|
_r(ctx, _C.termGlow, x+20, y+6, 8, 3);
|
||||||
|
_dot(ctx, '#00ff88', x+22, y+6);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _drawMapFallback(ctx) {
|
||||||
|
var T = 16;
|
||||||
|
|
||||||
|
// floor tiles
|
||||||
|
for (var ty = 0; ty < 20; ty++) {
|
||||||
|
for (var tx = 0; tx < 20; tx++) {
|
||||||
|
ctx.fillStyle = (tx + ty) % 2 === 0 ? _C.floorA : _C.floorB;
|
||||||
|
ctx.fillRect(tx * T, ty * T, T, T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conductor carpet
|
||||||
|
_r(ctx, _C.carpetBase, 16, 16, 288, 50);
|
||||||
|
_b(ctx, _C.carpetBorder, 18, 18, 284, 46);
|
||||||
|
|
||||||
|
// conductor desk
|
||||||
|
_r(ctx, _C.deskBack, 96, 20, 128, 30);
|
||||||
|
_r(ctx, _C.deskTop, 96, 20, 128, 12);
|
||||||
|
_r(ctx, _C.deskEdge, 96, 20, 128, 2);
|
||||||
|
_r(ctx, _C.deskEdge, 96, 20, 2, 30);
|
||||||
|
_r(ctx, _C.deskEdge, 222, 20, 2, 30);
|
||||||
|
_r(ctx, _C.deskShadow,96,48, 128, 4);
|
||||||
|
// monitor
|
||||||
|
_r(ctx, _C.monitorFrame, 138, 22, 44, 14);
|
||||||
|
_r(ctx, _C.monitorBlue, 140, 23, 40, 12);
|
||||||
|
_r(ctx, _C.monitorGlow, 156, 26, 8, 6);
|
||||||
|
_r(ctx, '#6699ff', 158, 27, 4, 3);
|
||||||
|
|
||||||
|
// workstations
|
||||||
|
_workstation(ctx, 16, 80); // S0
|
||||||
|
_workstation(ctx, 128, 80); // S1 (x+24 = 152 ≈ 144 center)
|
||||||
|
_workstation(ctx, 224, 80); // S2
|
||||||
|
_workstation(ctx, 16, 200); // S3
|
||||||
|
_workstation(ctx, 128, 200); // S4
|
||||||
|
|
||||||
|
// meeting rug
|
||||||
|
_r(ctx, _C.rugFill, 64, 130, 192, 62, 0.55);
|
||||||
|
_b(ctx, _C.rugBorder, 66, 132, 188, 58);
|
||||||
|
_b(ctx, '#202860', 70, 136, 180, 50);
|
||||||
|
|
||||||
|
// bulletin board (left wall)
|
||||||
|
_r(ctx, '#2c1a06', 18, 148, 36, 44);
|
||||||
|
_r(ctx, '#3a2508', 20, 150, 32, 40);
|
||||||
|
_r(ctx, '#cc9900', 22, 153, 12, 8);
|
||||||
|
_r(ctx, '#dd8800', 22, 164, 10, 6);
|
||||||
|
_r(ctx, '#bb7700', 34, 155, 13, 8);
|
||||||
|
_r(ctx, '#ccaa00', 33, 165, 11, 6);
|
||||||
|
_dot(ctx, '#ff4444', 28, 153);
|
||||||
|
_dot(ctx, '#44aaff', 41, 158);
|
||||||
|
_dot(ctx, '#44ff88', 27, 165);
|
||||||
|
|
||||||
|
// server rack (right wall)
|
||||||
|
_r(ctx, '#111122', 285, 80, 18, 112);
|
||||||
|
_r(ctx, '#181830', 287, 82, 14, 108);
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
var ry = 85 + i * 10;
|
||||||
|
_r(ctx, '#0a0a12', 288, ry, 12, 8);
|
||||||
|
var lc = ['#00ff44','#0044ff','#ff3300','#111111'][i % 4];
|
||||||
|
_r(ctx, lc, 296, ry + 2, 3, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// walls (drawn last to cover any overruns)
|
||||||
|
_r(ctx, _C.wallDark, 0, 0, 320, 16);
|
||||||
|
_r(ctx, _C.wallHighlight, 0, 14, 320, 2);
|
||||||
|
_r(ctx, _C.wallDark, 0, 0, 16, 320);
|
||||||
|
_r(ctx, _C.wallHighlight,14, 0, 2, 320);
|
||||||
|
_r(ctx, _C.wallDark, 304, 0, 16, 320);
|
||||||
|
_r(ctx, _C.wallHighlight,304, 0, 2, 320);
|
||||||
|
_r(ctx, _C.wallDark, 0, 304, 144, 16);
|
||||||
|
_r(ctx, _C.wallDark, 176, 304, 144, 16);
|
||||||
|
_r(ctx, _C.wallHighlight, 0, 304, 144, 2);
|
||||||
|
_r(ctx, _C.wallHighlight,176, 304, 144, 2);
|
||||||
|
|
||||||
|
// door
|
||||||
|
_r(ctx, '#0a0808', 144, 292, 32, 12); // outside (dark)
|
||||||
|
_r(ctx, _C.doorMid, 144, 280, 32, 24);
|
||||||
|
_r(ctx, _C.doorLight, 144, 280, 32, 3);
|
||||||
|
_r(ctx, _C.doorLight, 144, 280, 3, 24);
|
||||||
|
_r(ctx, _C.doorLight, 173, 280, 3, 24);
|
||||||
|
_r(ctx, '#4a2408', 146, 284, 12, 16); // door panels
|
||||||
|
_r(ctx, '#4a2408', 162, 284, 12, 16);
|
||||||
|
_r(ctx, _C.doorGold, 170, 291, 5, 5); // handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose map helpers for app.js runtime.
|
||||||
|
globalThis.MAP_POSITIONS = MAP_POSITIONS;
|
||||||
|
globalThis.loadMapAsset = loadMapAsset;
|
||||||
|
globalThis.drawMap = drawMap;
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -96,7 +96,7 @@
|
||||||
.demo-bar span { color: #3a4a80; }
|
.demo-bar span { color: #3a4a80; }
|
||||||
.demo-bar span.on { color: #4ade80; }
|
.demo-bar span.on { color: #4ade80; }
|
||||||
</style>
|
</style>
|
||||||
<script src="map.js"></script>
|
<script src="dist/map.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -456,3 +456,4 @@ loadMapAsset(function() {
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,3 +72,34 @@ Fields 表示バグが検出されずに ship された前科あり。
|
||||||
- `ORCH_ENABLED` の注入がテンプレートまたはビルド時変数で動作
|
- `ORCH_ENABLED` の注入がテンプレートまたはビルド時変数で動作
|
||||||
- `renderLogs()` のユニットテストが CI で実行される
|
- `renderLogs()` のユニットテストが CI で実行される
|
||||||
- `go build ./...` が変わらず動作 (embed パスの整合性)
|
- `go build ./...` が変わらず動作 (embed パスの整合性)
|
||||||
|
|
||||||
|
## 作業報告 (2026-03-05)
|
||||||
|
|
||||||
|
- 実装完了: タスク 1 / 2 / 3
|
||||||
|
- 主要変更:
|
||||||
|
- `pkg/miniapp/miniapp.go`
|
||||||
|
- `//go:embed static` に変更
|
||||||
|
- `/miniapp/` を `http.FileServer(http.FS(...))` で一括配信
|
||||||
|
- `/miniapp` / `/miniapp/index.html` は `html/template` で描画し `OrchEnabled` を注入
|
||||||
|
- 旧 `serveMapJS` 個別ルートを削除
|
||||||
|
- フロントエンド分離
|
||||||
|
- `pkg/miniapp/frontend/src/` に `app.js` / `styles.css` / `map.js` / `logs_view.js` を配置
|
||||||
|
- `pkg/miniapp/static/index.html` はテンプレート + 外部アセット参照へ縮小
|
||||||
|
- `pkg/miniapp/static/dist/` に `app.js` / `app.css` / `map.js` を生成
|
||||||
|
- バンドル導線
|
||||||
|
- `pkg/miniapp/frontend/build.mjs` + `package.json` を追加
|
||||||
|
- `go generate ./...` で `bun run --cwd frontend build` が実行されるよう接続
|
||||||
|
- Log viewer 強化
|
||||||
|
- `logs_view.js` へレンダリングロジックを抽出
|
||||||
|
- フィルタ/ページネーションを追加
|
||||||
|
- `vitest + happy-dom` による unit test を追加 (`logs_view.test.js`)
|
||||||
|
- CI 更新 (`.github/workflows/pr.yml`)
|
||||||
|
- Bun セットアップを追加
|
||||||
|
- frontend の `pnpm test` を追加
|
||||||
|
|
||||||
|
- 検証結果:
|
||||||
|
- `bun run --cwd pkg/miniapp/frontend build`: 成功
|
||||||
|
- `pnpm --dir pkg/miniapp/frontend test`: 成功
|
||||||
|
- `go generate ./...`: 成功
|
||||||
|
- `go test ./pkg/miniapp/...`: 成功
|
||||||
|
- `go test ./...`: 一部既存の環境依存テストが Windows 環境で失敗(TASK-4差分外)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue