Skip to content

feat(dashboard): add HTML gallery and image compare tools - #447

Draft
tembo[bot] wants to merge 2 commits into
mainfrom
tembo/html-gallery-image-compare
Draft

feat(dashboard): add HTML gallery and image compare tools#447
tembo[bot] wants to merge 2 commits into
mainfrom
tembo/html-gallery-image-compare

Conversation

@tembo

@tembo tembo Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

Adds two new workspace tools to the dashboard, each available from the sidebar (under Workspace).

1. HTML Gallery (/[slug]/gallery)

  • Upload .html files (drag-and-drop or file picker) and view them all on one page as live, sandboxed iframe previews in a gallery grid.
  • Copy to Figma per card and Copy all to Figma — writes the markup to the clipboard as text/html, which Figma's "paste from HTML" converts into native layers on paste.
  • Delete documents with a confirmation dialog.
  • Files are stored per-organization in a new html_documents table. HTML content is kept in the DB (rather than R2) so previews and the copy feature work without any object-storage round-trip or CORS setup. 5MB/file cap.

2. Image Compare (/[slug]/image-compare)

  • Upload two images (PNG/JPEG/WebP) and get a pixel-similarity percentage, computed entirely in the browser by drawing both onto a fixed 256×256 canvas and averaging per-channel differences.
  • A draggable before/after slider (pointer + keyboard range input) to visually compare the two, with a plain-language descriptor (e.g. "Very similar").
  • Fully client-side — no uploads, no persistence.

Implementation notes

  • New Drizzle table html_documents + relations, with generated migration 0043_whole_ultimo.sql.
  • New oRPC router html (list / create / deleteMany), registered in the dashboard router and scoped to the active organization via the existing authorizedProcedure + membership checks.
  • Two sidebar nav entries (HTML Gallery, Image Compare) using Hugeicons.
  • Pages follow the existing page.tsx / page-client.tsx / loading.tsx / skeleton.tsx convention.

Note: this replaces the originally-requested admin page, which was explicitly dropped per the follow-up instruction.

Screenshot/Recording (if applicable)

N/A — UI follows existing dashboard page/card conventions.

Checklist
  • I ran a self-review before opening this PR
  • I ran formatting/linting/type checks locally
  • I updated docs when behavior or setup changed
  • I only added comments where the logic is not obvious
  • I have used conventional commits for the PR title and commit messages
  • I did not use AI to write the code in this PR or have disclosed that I did — this PR was written by the Tembo agent

⚠️ A database migration (0043_whole_ultimo.sql) is included — run bun run db:migrate after merging.


Want tembo to make any changes? Add a comment with @tembo and i'll get back to work!

View on Tembo View Agent Settings


Summary by cubic

Adds two new workspace tools: HTML Gallery and Image Compare. Speeds up reviews with secure HTML previews you can paste into Figma, plus a fast pixel similarity check.

  • New Features

    • HTML Gallery (/[slug]/gallery)
      • Upload .html/.htm files and see live, sandboxed previews in a grid.
      • Copy one or all to Figma (rich text/html clipboard).
      • Delete with confirm. Stored per org in a new html_documents table (5MB/file).
      • Sidebar link under Workspace.
      • Polish: stricter iframe sandboxing, broader file-type accept, clearer empty/disabled states.
    • Image Compare (/[slug]/image-compare)
      • Upload two images (PNG/JPEG/WebP) and get a pixel-similarity score.
      • Draggable before/after slider with a plain-language label.
      • Fully client-side. No uploads or persistence.
      • Sidebar link under Workspace.
  • Migration

    • Run: bun run db:migrate (creates html_documents and indexes).

Written for commit 79a2013. Summary will update on new commits.

Review in cubic

Co-authored-by: Dominik <68947960+mezotv@users.noreply.github.com>
@tembo
tembo Bot requested a review from mezotv as a code owner June 22, 2026 20:09
@tembo tembo Bot added the tembo Pull request created by Tembo label Jun 22, 2026
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
notra Ready Ready Preview, Comment Jun 24, 2026 12:19am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
notra-web Skipped Skipped Jun 24, 2026 12:19am

Request Review

@github-actions

Copy link
Copy Markdown

React Doctor found 22 issues in 3 files · 4 errors & 18 warnings · score 72 / 100 (Needs work) · vs main

Errors

18 warnings

src/app/(dashboard)/[slug]/gallery/page-client.tsx

  • ⚠️ L149 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L157 Mutation without cache invalidation query-mutation-missing-invalidation
  • ⚠️ L161 await inside a loop async-await-in-loop
  • ⚠️ L183 Mutation without cache invalidation query-mutation-missing-invalidation
  • ⚠️ L199 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L225 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L237 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L247 Missing effect dependencies exhaustive-deps
  • ⚠️ L274 Control missing accessible label control-has-associated-label

src/app/(dashboard)/[slug]/image-compare/page-client.tsx

  • ⚠️ L64 Control missing accessible label control-has-associated-label
  • ⚠️ L89 Role used instead of HTML tag prefer-tag-over-role
  • ⚠️ L134 Many related useState calls prefer-useReducer
  • ⚠️ L140 State only used in handlers rerender-state-only-in-handlers
  • ⚠️ L150 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L162 Redundant manual memoization react-compiler-no-manual-memoization
  • ⚠️ L173 Multiple setState calls in one effect no-cascading-set-state
  • ⚠️ L216 Redundant manual memoization react-compiler-no-manual-memoization

src/components/dashboard/nav-main.tsx

  • ⚠️ L122 Redundant manual memoization react-compiler-no-manual-memoization

Reviewed by React Doctor for commit 29d8a59. See inline comments for fixes.

const { data, isLoading } = useQuery(listQueryOptions);
const documents: HtmlDocument[] = data?.documents ?? [];

const invalidate = useCallback(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

This useCallback is dead weight, since React Compiler already caches every function here. Delete it.

Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.

Docs

[queryClient]
);

const uploadMutation = useMutation({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/query-mutation-missing-invalidation (warning)

useMutation with no cache update leaves your users looking at stale data after it runs.

Fix → Add onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] }) so cached data stays in sync after the mutation

Docs

mutationFn: async (files: File[]) => {
let uploaded = 0;
for (const file of files) {
const content = await file.text();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/async-await-in-loop (warning)

This makes the for…of loop slow because each await runs one after another, so collect the independent calls & run them together with await Promise.all(items.map(...))

Fix → Collect the items, then use await Promise.all(items.map(...)) so independent work runs at the same time

Docs

},
});

const deleteMutation = useMutation({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/query-mutation-missing-invalidation (warning)

useMutation with no cache update leaves your users looking at stale data after it runs.

Fix → Add onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] }) so cached data stays in sync after the mutation

Docs

},
});

const handleFiles = useCallback(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

This useCallback is dead weight, since React Compiler already caches every function here. Delete it.

Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.

Docs

beforeRef.current = before;
afterRef.current = after;

const handleSelect = useCallback((side: Side, file: File) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

This useCallback is dead weight, since React Compiler already caches every function here. Delete it.

Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.

Docs

});
}, []);

const handleClear = useCallback((side: Side) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

This useCallback is dead weight, since React Compiler already caches every function here. Delete it.

Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.

Docs

setSimilarity(null);
}, []);

useEffect(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-cascading-set-state (warning)

5 setState calls in one useEffect redraw your screen each time they run together.

Fix → Combine related updates in useReducer so one effect does not redraw the screen once per setState call.

Docs


useEffect(() => {
if (!(before && after)) {
setSimilarity(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-hooks-js/set-state-in-effect (error)

This component misses React Compiler's automatic memoization & re-renders more than it should: Calling setState synchronously within an effect can trigger cascading renders. Rewrite the flagged code so the compiler can optimize it.

Fix → Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:

  • Update external systems with the latest state from React.
  • Subscribe for updates from some external system, calling setState in a callback function when external state changes.

Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).

Docs

[]
);

const updatePosition = useCallback((clientX: number) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/react-compiler-no-manual-memoization (warning)

This useCallback is dead weight, since React Compiler already caches every function here. Delete it.

Fix → Delete the useMemo / useCallback / memo call and use the plain value or component. React Compiler caches it for you.

Docs

@mezotv
mezotv marked this pull request as draft June 22, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tembo Pull request created by Tembo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant