Skip to content

feat(web): show a project's chosen or first mockup on its tile - #148

Merged
brianjfox merged 3 commits into
mainfrom
feat/tile-mockup
Nov 4, 2026
Merged

brianjfox merged 3 commits into
mainfrom
feat/tile-mockup

Conversation

@brianjfox

@brianjfox brianjfox commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Closes #147

What

A project's tile shows its mockup: the first page of the design the person chose, or of the first concept filed before a choice. A project with no mockup keeps its pattern.

Why

The cover was a generated pattern whatever the project looked like. Details in #147.

Changes

  • apps/web/src/pages/project-cover.tsx: coverArtifact picks the chosen design, else the first concept; ProjectCover reads it once the tile is within a screen of the viewport, builds the first page the way the themes pane does (firstPage), and shows it in an inert scripted frame over the pattern. The list's small swatch shows the pattern only.
  • apps/web/src/pages/project-library.tsx, project-library.css: the tile uses it; the frame lays the page out at four times the tile's width and scales it down, as the theme thumbnails do.
  • apps/web/src/queries/keys.ts: projectCover.
  • project-cover.test.ts covers the pick.

A tile's cover was a generated pattern whatever the project looked like. A project with a mockup shows its first page, the design the person chose or the first concept filed, as a still picture read once the tile is near the screen; a project without one keeps the pattern.
@corbits-triage

corbits-triage Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Triage: Pass

No changes needed from you. A maintainer will review this pull request.

Approve held: code review has 2 findings at P2 or above open on this head.

@brianjfox
brianjfox changed the base branch from feat/library-view-toggle to main November 4, 2026 09:46
Comment thread apps/web/src/pages/project-cover.tsx Outdated
if (pick === null) return null;
const filed = await readArtifact(projectId, pick.artifactId, pick.version);
const files = parseBundle(filed.content);
return files === null ? null : firstPage(files);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Empty first page shows 'No design to show.' instead of keeping the pattern

If the bundle has no designs/index.html and no screen fragments, firstPage returns an empty string. The component only checks html === null, so it sets data-mockup and renders GuiFrame. For an empty string, GuiFrame renders the text note 'No design to show.' on the tile instead of leaving the pattern. The PR says a project with no mockup keeps its pattern.

The chosen design or first concept is a bundle without designs/index.html (or one whose index page is empty), so firstPage returns "".

Resolve this comment. Triage will not finish while it is open.

Comment on lines +233 to 238
<ProjectCover
projectId={project.id}
pattern={cover.pattern}
style={cover.style}
aria-hidden="true"
mockup={view === "grid"}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Tile shows the text "No design to show." when the bundle has no index page or an empty one

readCoverPage returns firstPage(files), and firstPage falls back to "" when designs/index is missing. Because "" is not null, ProjectCover sets data-mockup and renders GuiFrame. GuiFrame treats empty HTML as no design and renders a

No design to show.

inside the tile's cover, in place of the pattern the PR says should remain.

A project whose chosen design or first concept has a bundle with no designs/index page (for example a concept filed before its index was written, or an empty bundle), viewed in grid mode.

Resolve this comment. Triage will not finish while it is open.

Comment thread apps/web/src/pages/project-library.css Outdated
height: 400%;
min-height: 0;
border: 0;
background: var(--wb-background);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 · Frame gets the app's theme background, so pages without their own background break in dark mode

Everywhere else .gui-frame uses #fff, which is the default for a standalone document. With var(--wb-background), a mockup that sets no body background is shown on the app's dark surface in dark mode. Its default black text is then almost unreadable, and the tile no longer matches what the themes pane shows.

Dark mode with a mockup whose first page does not set its own body background.

Resolve this comment. Triage will not finish while it is open.

Comment thread apps/web/src/pages/project-library.css Outdated
height: 400%;
min-height: 0;
border: 0;
background: var(--wb-background);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 · Opaque frame background hides the pattern while the page loads

The comment says the pattern stays visible beneath while the page loads. But the iframe is inserted as soon as the HTML arrives and its background is opaque, so a blank block covers the pattern until the scripted page renders.

Any tile whose mockup query succeeds while the iframe document is still loading or running its scripts.

Resolve this comment. Triage will not finish while it is open.

…and a white frame

A bundle with no index page produced an empty first page, which the frame showed as 'No design to show.' over the tile; the tile keeps its pattern instead. The frame is white like every other mockup frame, so a page without its own background reads the same in dark mode.
@brianjfox
brianjfox merged commit 663b3f6 into main Nov 4, 2026
9 checks passed
Comment on lines +37 to +41
const { artifacts } = await listProjectArtifacts(projectId);
const pick = coverArtifact(artifacts);
if (pick === null) return null;
const filed = await readArtifact(projectId, pick.artifactId, pick.version);
const files = parseBundle(filed.content);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Each tile calls the heavy room-listing endpoint and downloads the whole design bundle

listProjectArtifacts hits the hub's service.list. That handler queries workflow runs and pending approvals across the tenant, reads failure details for failed runs, and runs approvedVersion and filedAt queries for every artifact. readArtifact then downloads the full design bundle. All of this is only to show a thumbnail, and it runs once for every grid tile that comes near the viewport.

A library with many projects in grid view, scrolled through: one full room listing and one full bundle download per tile.

Resolve this comment. Triage will not finish while it is open.

Comment on lines +233 to +237
<ProjectCover
projectId={project.id}
pattern={cover.pattern}
style={cover.style}
aria-hidden="true"
mockup={view === "grid"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Tile can keep showing an old mockup after the project's design changes

The cover query's key is just the projectId, it stays fresh for 5 minutes, and nothing anywhere calls invalidate on keys.projectCover. Its key also doesn't include anything like project.lastActivityAt. So after a person picks a design or a new concept is filed, going back to the library shows the cached cover from before.

Open the library so covers load, open a project, choose a design or file a concept, then return to the library within 5 minutes. The tile still shows the earlier concept, or the plain pattern if the project had no mockup before.

Resolve this comment. Triage will not finish while it is open.

pattern={cover.pattern}
style={cover.style}
aria-hidden="true"
mockup={view === "grid"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 · Grid view runs a scripted iframe for every tile and never unmounts them

Each grid tile that has a mockup mounts a GuiFrame iframe with scripts on. Once a tile gets near the viewport, its iframe stays mounted even after the tile scrolls away. On a large library this means many live documents running the designs' scripts and animations at once, which costs a lot of memory and CPU.

Scroll through a library with dozens of projects in grid view.

Resolve this comment. Triage will not finish while it is open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Project tiles should show the project's chosen (or first) mockup

1 participant