diff --git a/README.md b/README.md index 723c046..4d0c121 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,6 @@ Lakebed local state resets when the dev process restarts. Real Google sign-in ac ## How it works -The project is a Lakebed v0 capsule. Lakebed supplies first-party Google authentication, transactional storage, and hosting. Artifact HTML is split into database-safe chunks. Owner, workspace-viewer, and recipient invitations bind to durable Lakebed user IDs on first matching sign-in. HTML is rendered with `srcDoc` in a sandboxed iframe without `allow-same-origin`. +The project is a Lakebed v0 capsule. Lakebed supplies first-party Google authentication, transactional storage, and hosting. Artifact HTML is split into database-safe chunks. Owner, workspace-viewer, and recipient invitations bind to durable Lakebed user IDs on first matching sign-in. HTML is rendered with `srcDoc` in a sandboxed iframe without `allow-same-origin`. The preview injects an `about:srcdoc` base URL at render time so fragment links stay within the artifact; stored and downloaded HTML remains unchanged. Codex Artifacts is an independent project and is not affiliated with or endorsed by OpenAI or Anthropic. diff --git a/client/index.tsx b/client/index.tsx index 288addd..a534218 100644 --- a/client/index.tsx +++ b/client/index.tsx @@ -28,6 +28,7 @@ import { const client = createClient(); const DEFAULT_DOCUMENT_TITLE = "Codex Artifacts"; const KNOWN_EMAILS_KEY = "codex-artifacts:known-emails"; +const SRCDOC_BASE = ''; const EXPIRATION_OPTIONS = [ { label: "1 hour", value: "3600" }, { label: "1 day", value: "86400" }, @@ -66,6 +67,28 @@ function messageFromError(error: unknown): string { return error instanceof Error ? error.message : "Something went wrong."; } +function withSrcdocBase(html: string): string { + const head = /]*)?>/i.exec(html); + if (head) { + const insertionPoint = head.index + head[0].length; + return `${html.slice(0, insertionPoint)}${SRCDOC_BASE}${html.slice(insertionPoint)}`; + } + + const documentElement = /]*)?>/i.exec(html); + if (documentElement) { + const insertionPoint = documentElement.index + documentElement[0].length; + return `${html.slice(0, insertionPoint)}${SRCDOC_BASE}${html.slice(insertionPoint)}`; + } + + const doctype = /]*)?>/i.exec(html); + if (doctype) { + const insertionPoint = doctype.index + doctype[0].length; + return `${html.slice(0, insertionPoint)}${SRCDOC_BASE}${html.slice(insertionPoint)}`; + } + + return `${SRCDOC_BASE}${html}`; +} + function readKnownEmails(): string[] { try { const value = JSON.parse(window.localStorage.getItem(KNOWN_EMAILS_KEY) ?? "[]"); @@ -105,15 +128,15 @@ function accessLabel(access: { function SignInCard({ shared = false }: { shared?: boolean }) { return (
-
-

Private workspace

+
+

Private workspace

{shared ? "Sign in to open this artifact" : "Your Codex artifacts, in one quiet place."}

-

+

Access is checked against a verified Google email. Shared links do not make their contents public.

- +
); @@ -122,19 +145,19 @@ function SignInCard({ shared = false }: { shared?: boolean }) { function AppHeader() { const auth = useAuth(); return ( -
+
- A + Codex Artifacts - private by default + private by default {!auth.isGuest && !auth.isLoading ? (
- {auth.email ?? auth.displayName} - + {auth.email ?? auth.displayName} +
) : null}
@@ -183,30 +206,30 @@ function NewArtifactForm() { } return ( -
+
-

New artifact

+

New artifact

Publish an HTML file

-

Artifacts expire after three days by default, freeing their storage automatically. Owners and workspace viewers can open every artifact; additional recipients can be added per artifact.

-

Maximum {formatBytes(MAX_ARTIFACT_BYTES)} per artifact; {formatBytes(MAX_TOTAL_ARTIFACT_BYTES)} workspace HTML budget.

+

Artifacts expire after three days by default, freeing their storage automatically. Owners and workspace viewers can open every artifact; additional recipients can be added per artifact.

+

Maximum {formatBytes(MAX_ARTIFACT_BYTES)} per artifact; {formatBytes(MAX_TOTAL_ARTIFACT_BYTES)} workspace HTML budget.

void submit(event)}> -
{error ?

{error}

: null} @@ -277,38 +300,38 @@ function ArtifactCard({ artifact }: { artifact: OwnedArtifact }) { } return ( -
+
- {artifact.title} -

{formatBytes(Number(artifact.sizeBytes))} · updated {formatDate(artifact.updatedAt)}

-

{expirationLabel(artifact.expiresAt)}

+ {artifact.title} +

{formatBytes(Number(artifact.sizeBytes))} · updated {formatDate(artifact.updatedAt)}

+

{expirationLabel(artifact.expiresAt)}

- + {accessLabel(artifact)}
- Open & manage access - -
-
-
); @@ -328,23 +351,23 @@ function OwnerDashboard() {
-
+
-

Library

+

Library

Published artifacts

- + {artifacts?.length ?? 0} total · {formatBytes(usedBytes)} of {formatBytes(MAX_TOTAL_ARTIFACT_BYTES)}
{!artifacts ? ( -
Loading artifacts…
+
Loading artifacts…
) : artifacts.length ? (
{artifacts.map((artifact) => )}
) : ( -
-

No artifacts yet.

-

Publish an HTML file above or use the automation script.

+
+

No artifacts yet.

+

Publish an HTML file above or use the automation script.

)}
@@ -355,11 +378,11 @@ function OwnerDashboard() { function NonOwnerHome({ isWorkspaceViewer }: { isWorkspaceViewer: boolean }) { return (
-

Signed in

+

Signed in

{isWorkspaceViewer ? "Workspace viewer access is active." : "Open the link that was shared with you."}

-

+

{isWorkspaceViewer ? "You can open every artifact link in this workspace, without management access." : "Additional access is granted per artifact and verified Google email."} @@ -478,20 +501,28 @@ function AccessControl({ artifact }: { artifact: ViewedArtifact }) { } } + const accessDescription = `Access · ${accessLabel({ + isPublic, + sharedWith: emails, + sharedDomains: domains, + workspaceViewerCount: artifact.workspaceViewerEmails.length + })}`; + return (

{open ? (
@@ -504,7 +535,7 @@ function AccessControl({ artifact }: { artifact: ViewedArtifact }) {
) : null} @@ -613,6 +644,10 @@ function ArtifactFrame({ slug }: { slug: string }) { if (!artifact) return ""; return URL.createObjectURL(new Blob([artifact.html], { type: "text/html;charset=utf-8" })); }, [artifact?.html]); + const previewHtml = useMemo( + () => artifact ? withSrcdocBase(artifact.html) : "", + [artifact?.html] + ); useEffect(() => { if ( @@ -701,26 +736,88 @@ function ArtifactFrame({ slug }: { slug: string }) { return (
-
-
- ← -
-

{artifact.title}

-

{formatBytes(artifact.sizeBytes)} · {expirationLabel(artifact.expiresAt)} · sandboxed preview

+
+
+ + + +
+

{artifact.title}

+
+ + + +
+

{formatBytes(artifact.sizeBytes)}

+

{expirationLabel(artifact.expiresAt)}

+
+
-
+
{artifact.canManage ? : null} - - Download HTML - {!auth.isGuest ? : null} + + + + Download HTML + + {!auth.isGuest ? ( + + ) : null}