feat(console-web): replace TanStack UI with Solid host pages

- move the management frontend under the wasmeld-console crate
- use a persistent Host shell with disposable iframe page documents
- version Host/Page postMessage state and command contracts
- migrate runtime, service, WIT, invocation, and settings workflows
- add Tailwind CSS v4, responsive layouts, and Host-owned dialogs
- emit a dependency-free PWA worker with API cache exclusions
- remove the superseded root TanStack Start application
This commit is contained in:
Maofeng
2026-07-30 09:21:55 +08:00
parent 4e32a1246b
commit 70ee5e33da
49 changed files with 3996 additions and 5648 deletions
@@ -0,0 +1,105 @@
import { CloudUpload, Download, FileCode2, Search } from "lucide-solid";
import { createMemo, For, Show } from "solid-js";
import { EmptyState } from "../../components/ui/empty-state";
import { PageHeading } from "../../components/ui/page-heading";
import { witPackageDownloadUrl } from "../../lib/api";
import type { PageProps } from "../types";
export default function WitPackagesPage(props: PageProps) {
const packages = createMemo(() => {
const state = props.state();
const query = state?.query.trim().toLowerCase() ?? "";
return (
state?.snapshot?.witPackages.filter(
(item) =>
!query ||
item.name.toLowerCase().includes(query) ||
item.version.toLowerCase().includes(query) ||
item.sha256.toLowerCase().includes(query),
) ?? []
);
});
return (
<main class="page">
<PageHeading
eyebrow="INTERFACES"
title="WIT 包"
description="管理不可变、版本化的 Component 接口依赖。"
actions={
<button
class="btn btn-primary"
onClick={() => props.command({ type: "open-wit-publish" })}
>
<CloudUpload size={16} />
WIT
</button>
}
/>
<section class="panel">
<div class="panel-header">
<div>
<h2>Registry</h2>
<p>{packages().length} </p>
</div>
</div>
<Show
when={packages().length > 0}
fallback={
<EmptyState icon={Search} title="没有匹配的 WIT 包" detail="发布或调整搜索关键词。" />
}
>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th>SHA-256</th>
<th>
<span class="sr-only"></span>
</th>
</tr>
</thead>
<tbody>
<For each={packages()}>
{(item) => (
<tr>
<td>
<span class="flex items-center gap-2">
<FileCode2 size={15} class="text-cyan-strong" />
<strong>{item.name}</strong>
</span>
</td>
<td class="code">{item.version}</td>
<td class="text-xs text-muted">
{item.dependencies.length
? item.dependencies
.map((dependency) => `${dependency.name}@${dependency.version}`)
.join(", ")
: "无"}
</td>
<td class="code max-w-64 truncate text-muted" title={item.sha256}>
{item.sha256}
</td>
<td class="text-right">
<a
class="icon-btn"
title="下载"
href={witPackageDownloadUrl(props.state()?.apiBase ?? "", item)}
>
<Download size={15} />
</a>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</Show>
</section>
</main>
);
}