feat: ship a web app (#218)

* feat(orion): add endpoint for link metadata

* refactor: cleanup comments

* wip: plumbing for building a web app

* setup deployment materials for web app

* fix(web): favicon
This commit was merged in pull request #218.
This commit is contained in:
Arjun Patel
2026-05-26 15:37:35 -07:00
committed by GitHub
parent 173c63508a
commit 64f02d1c3b
51 changed files with 1260 additions and 238 deletions
@@ -9,6 +9,7 @@ import {
getAttachmentHandler,
type AttachmentItem,
} from "@/features/attachments/attachment-lightbox";
import { platform } from "@/lib/platform";
export interface PendingAttachment {
id: string;
@@ -120,7 +121,7 @@ function LinkPreviewThumbnail({ entry }: { entry: LinkPreviewEntry }) {
return (
<button
type="button"
onClick={() => window.electronLink.openExternal(metadata.url)}
onClick={() => platform.link.openExternal(metadata.url)}
className="flex h-16 w-28 shrink-0 flex-col justify-center gap-1 overflow-hidden rounded-lg bg-white/10 px-2 py-1.5 text-left transition-colors hover:bg-white/15"
>
<div className="flex items-center gap-1 text-[10px] text-white/40">
@@ -23,6 +23,8 @@ import { MAX_ATTACHMENT_SIZE_BYTES, MAX_ATTACHMENTS } from "@/lib/constants";
import type { PendingAttachment } from "@/features/compose/attachment-strip";
import { useSuspendPlayback } from "@/hooks/use-suspend-playback";
import { useComposeIntentStore } from "@/stores/compose-intent-store";
import { platform } from "@/lib/platform";
import { requireDesktop } from "@/lib/platform/desktop-only";
export type ComposeStep = "idle" | "picking" | "recording" | "reviewing" | "typing" | "configuring" | "submitting";
@@ -512,6 +514,7 @@ export function ComposeOverlay({
} else if (e.key === "s" || e.key === "S") {
e.preventDefault();
if (!guardIdle()) break;
if (!requireDesktop("Screen recording")) break;
setRecordingSource("screen");
setStepSync("picking");
} else if (e.key === "t" || e.key === "T") {
@@ -594,7 +597,7 @@ export function ComposeOverlay({
<ScreenSourcePicker
title="Record your screen"
confirmLabel="Record"
getSources={window.electronScreen.getScreenSources}
getSources={platform.screenRecord.getScreenSources}
onSelect={handleScreenSourceSelected}
onCancel={cancel}
/>
@@ -1,4 +1,6 @@
import { useCallback, useEffect, useRef } from "react";
import { platform } from "@/lib/platform";
import { requireDesktop } from "@/lib/platform/desktop-only";
const VIDEO_PREFERRED_MIME = "video/webm;codecs=vp9,opus";
const VIDEO_FALLBACK_MIME = "video/webm";
@@ -75,6 +77,7 @@ export function useScreenRecorder({
const startRecording = useCallback(
async (sourceId: string) => {
if (!requireDesktop("Screen recording")) return;
try {
// 1. Screen video
const screenStream = await navigator.mediaDevices.getUserMedia({
@@ -113,7 +116,7 @@ export function useScreenRecorder({
const durationMs = Date.now() - startTimeRef.current;
const blob = new Blob(chunksRef.current, { type: mime });
stopAllTracks();
window.electronScreen.stopRecordingWindow();
platform.screenRecord.cancel();
if (blob.size > 0) {
onFinishRef.current(blob, durationMs, mime);
@@ -123,17 +126,17 @@ export function useScreenRecorder({
recorder.start(1000);
// 4. Show floating control window
window.electronScreen.startRecordingWindow();
platform.screenRecord.start();
// 5. Listen for stop from floating window
cleanupIpcRef.current = window.electronScreen.onStopRequested(() => {
cleanupIpcRef.current = platform.screenRecord.onStopRequested(() => {
if (recorderRef.current?.state === "recording") {
recorderRef.current.stop();
}
});
} catch (err) {
stopAllTracks();
window.electronScreen.stopRecordingWindow();
platform.screenRecord.cancel();
onErrorRef.current(
err instanceof Error ? err.message : "Failed to start screen recording",
);
@@ -157,14 +160,14 @@ export function useScreenRecorder({
}
}
stopAllTracks();
window.electronScreen.stopRecordingWindow();
platform.screenRecord.cancel();
}, [stopAllTracks]);
// Cleanup on unmount
useEffect(() => {
return () => {
stopAllTracks();
window.electronScreen.stopRecordingWindow();
platform.screenRecord.cancel();
};
}, [stopAllTracks]);