feat: phase 5 distribution — tauri shell, pwa, desktop release artifacts

- apps/desktop: Tauri 2 thin shell; connect screen stores the deployment
  URL, webview renders the remote console, tray with Show/Change Server/
  Quit, close-to-tray, single-instance
- frontend: vite-plugin-pwa generates manifest + Workbox SW (shell
  precache, /api /auth /mailpit NetworkOnly), install banner on
  beforeinstallprompt, rasterized PWA icons
- release.yml: desktop bundle matrix (linux/macos/windows) attached to
  GitHub Releases
- workspace 0.5.0; Dockerfiles copy the desktop workspace manifest

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomas Dvorak committed 2026-09-16 19:59:53 +02:00
1 parent 23186e38b8
commit deb56d9a23
55 files changed
+9657 -155

No files matched your search

+2
View File
@@ -31,6 +31,7 @@ import {
NetworkError,
OnboardingModal,
ProjectDashboard,
PwaInstallBanner,
type PaletteCommand,
type ViewType,
} from "./components";
@@ -1500,6 +1501,7 @@ export default function App() {
<Show when={platformMessage()}>
<div class="message message-neutral mb-5">{platformMessage()}</div>
</Show>
<PwaInstallBanner />
<Show
when={platform() || isDemo}
@@ -0,0 +1,58 @@
import { Show, createSignal, onCleanup, onMount } from "solid-js";
// Chromium fires `beforeinstallprompt` once manifest + service worker pass
// installability checks. Stash the event and let the banner trigger it —
// the native mini-infobar is easy to miss.
interface BeforeInstallPromptEvent extends Event {
prompt(): Promise<void>;
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
}
const DISMISS_KEY = "primora_pwa_install_dismissed";
export function PwaInstallBanner() {
const [deferred, setDeferred] = createSignal<BeforeInstallPromptEvent | null>(null);
const [dismissed, setDismissed] = createSignal(localStorage.getItem(DISMISS_KEY) === "1");
onMount(() => {
const handler = (event: Event) => {
event.preventDefault();
// SAFETY: the listener is only attached to "beforeinstallprompt", whose
// event always carries prompt()/userChoice.
setDeferred(event as BeforeInstallPromptEvent);
};
window.addEventListener("beforeinstallprompt", handler);
onCleanup(() => window.removeEventListener("beforeinstallprompt", handler));
});
const install = async () => {
const event = deferred();
if (!event) return;
await event.prompt();
if ((await event.userChoice).outcome === "accepted") setDeferred(null);
};
const dismiss = () => {
localStorage.setItem(DISMISS_KEY, "1");
setDismissed(true);
};
return (
<Show when={deferred() && !dismissed()}>
<div
class="message message-info mb-5"
style="display:flex;align-items:center;justify-content:space-between;gap:1rem"
>
<span>Install Primora as an app for quick access from your launcher.</span>
<span style="display:flex;gap:0.5rem;flex-shrink:0">
<button class="btn btn-primary btn-sm" onClick={install}>
Install
</button>
<button class="btn btn-ghost btn-sm" onClick={dismiss}>
Not now
</button>
</span>
</div>
</Show>
);
}
+1
View File
@@ -13,6 +13,7 @@ export { Modal, ModalFooter } from "./Modal";
export { NotificationCenter, addNotification, removeNotification, clearNotifications, notify } from "./NotificationCenter";
export { OnboardingModal } from "./OnboardingModal";
export { Progress, CircularProgress, Spinner } from "./Progress";
export { PwaInstallBanner } from "./PwaInstallBanner";
export { ProjectDashboard } from "./ProjectDashboard";
export { Table, DataTable, Pagination } from "./Table";
export { Tabs, TabPanel } from "./Tabs";