Skip to content

feat: implement snippet sharing#16

Merged
mattiacerutti merged 5 commits intomainfrom
feat/share-snippet
Nov 23, 2025
Merged

feat: implement snippet sharing#16
mattiacerutti merged 5 commits intomainfrom
feat/share-snippet

Conversation

@mattiacerutti
Copy link
Copy Markdown
Owner

@mattiacerutti mattiacerutti commented Nov 23, 2025

Summary by CodeRabbit

  • New Features

    • Share button to copy links that open the game with the current snippet.
    • Direct snippet activation from a URL parameter with user-facing fallback and feedback.
    • In-app toast notifications for success/error messages and actions; improved loading boundary for smoother initial load.
  • Dependencies

    • Added Sonner for toast notifications.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 23, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds snippet-sharing via URL, Toaster notifications (sonner), a tryCatch utility, snippet id in snippet types, server-side snippet-by-ID fetching and TRPC procedure, and client flows to activate a language using a snippet from the URL or a shared link.

Changes

Cohort / File(s) Summary
Dependency
package.json
Added sonner dependency.
Layout
src/app/game/layout.tsx
Inserted Toaster (bottom-right, single visible toast, custom styling) and wrapped children in Suspense.
Page init & URL handling
src/app/game/page.tsx
Reads snippet URL param and, if present, triggers new snippet-based activation flow on load.
Game UI — Share & Actions
src/features/game/components/game-view.tsx
Added share button and handleShareSnippet to build a snippet URL, copy to clipboard, and show success/error toasts; adjusted action layout.
Snippets hook & activation
src/features/game/hooks/useGameSnippets.ts
Added fetchSnippetById mutation usage and new activateLanguageWithSnippet(snippetId, defaultLanguage) helper; returns new method and uses toast + tryCatch for error handling and fallback.
Snippet types
src/features/shared/types/snippet.ts
Added id: string to IRawSnippet.
Snippet repository
src/features/snippets/infrastructure/repositories/snippet.repository.server.ts
Added findSnippetById(snippetId) exported function (returns {id, content, languageId} or null); removed explicit Prisma type annotation on findRandomSnippets.
Snippet services
src/features/snippets/services/get-snippets.server.ts
getRandomSnippets now includes id; added getSnippetById(snippetId) service that computes disabledRanges and returns snippet shape or null.
TRPC router
src/server/trpc/routers/snippet.ts
Added byId procedure: validates UUID input, calls getSnippetById, throws NOT_FOUND if absent, returns snippet when found.
Utilities
src/utils.ts
New exported tryCatch<T, E = Error>(promise: Promise<T>) helper returning a Result tuple `[data, null]

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to review closely:
    • activateLanguageWithSnippet fallback/error flow and toast semantics (src/features/game/hooks/useGameSnippets.ts)
    • TRPC byId procedure input validation and NOT_FOUND handling (src/server/trpc/routers/snippet.ts)
    • Correct mapping/propagation of id and languageId across repo → service → client (snippet.repository.server.ts, get-snippets.server.ts, types)
    • tryCatch semantics and its usage sites to ensure callers handle the tuple form correctly (src/utils.ts and callers)

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement snippet sharing' directly and accurately describes the main change—adding snippet sharing functionality throughout the codebase, including share buttons, URL parameter handling, and related infrastructure.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae93478 and 3a9822e.

📒 Files selected for processing (1)
  • src/app/game/layout.tsx (1 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/features/snippets/infrastructure/repositories/snippet.repository.server.ts (2)

3-19: Consider keeping an explicit return type for findRandomSnippets

Right now the return type is inferred from the Prisma call. For a core repository method, an explicit return type (e.g. a small RandomSnippet type with {id, content}) can help prevent accidental contract changes if the Prisma query is modified later.


21-47: findSnippetById shape looks good; ensure relations are required

The query and returned shape ({id, content, languageId}) look correct and nicely flattened. Just make sure your Prisma schema guarantees that fileVersion and file are always present for any existing snippet; otherwise snippet.fileVersion.file.languageId would throw at runtime if those relations are nullable or missing. If they’re not strictly required, consider a defensive null-check or tightening the schema.

src/utils.ts (1)

1-20: tryCatch works; could be simplified to tuple types

The helper behaves as intended and is a nice fit for const [data, error] = await tryCatch(...). If you want to simplify the types, you could model the result as literal tuples instead of array interfaces, e.g.:

type Result<T, E = Error> = [T, null] | [null, E];

export async function tryCatch<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> {
  try {
    const data = await promise;
    return [data, null];
  } catch (error) {
    return [null, error as E];
  }
}

The current version is still perfectly valid, so this is purely a style/clarity tweak.

src/features/game/hooks/useGameSnippets.ts (1)

30-32: First assumption verified; second concern remains valid for robustness

The TRPC snippet.byId router explicitly throws TRPCError when the snippet doesn't exist, so your first assumption is confirmed—mutateAsync will correctly reject and tryCatch handles it properly.

However, the second assumption still warrants attention:

  • const language = availableLanguages![snippet.languageId]; (line 65)
    The hook uses a non-null assertion without checking that availableLanguages is loaded. While the current caller (page.tsx) guards this before invoking activateLanguageWithSnippet, the hook doesn't protect itself. If reused elsewhere without that guard in the future, it could crash. A simple check like if (!availableLanguages || !availableLanguages[snippet.languageId]) would make it self-contained.

  • Error messaging (lines 57–62) treats all mutation errors the same. A network timeout or server error will show "Snippet not found", which may confuse users. Consider distinguishing between retrieval failures and missing snippets if feasible.

src/server/trpc/routers/snippet.ts (1)

34-34: Consider simplifying the variable assignment.

The intermediate variable assignment is redundant and could be simplified for better readability.

Apply this diff:

-      const snippetId = input.snippetId;
-      const snippet = await getSnippetById(snippetId);
+      const snippet = await getSnippetById(input.snippetId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 160b4c5 and 5667615.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json (1 hunks)
  • src/app/game/layout.tsx (1 hunks)
  • src/app/game/page.tsx (5 hunks)
  • src/features/game/components/game-view.tsx (2 hunks)
  • src/features/game/hooks/useGameSnippets.ts (4 hunks)
  • src/features/shared/types/snippet.ts (1 hunks)
  • src/features/snippets/infrastructure/repositories/snippet.repository.server.ts (2 hunks)
  • src/features/snippets/services/get-snippets.server.ts (3 hunks)
  • src/server/trpc/routers/snippet.ts (2 hunks)
  • src/utils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/game/page.tsx (3)
src/features/game/hooks/useGameSnippets.ts (1)
  • useGameSnippets (14-112)
src/features/shared/types/language.ts (1)
  • ILanguage (1-6)
src/features/game/state/game-store.ts (1)
  • language (55-70)
src/features/snippets/services/get-snippets.server.ts (2)
src/features/shared/types/snippet.ts (1)
  • IRawSnippet (5-9)
src/features/snippets/infrastructure/repositories/snippet.repository.server.ts (1)
  • findSnippetById (21-48)
src/features/game/hooks/useGameSnippets.ts (4)
src/features/shared/types/language.ts (1)
  • ILanguage (1-6)
src/utils.ts (1)
  • tryCatch (13-20)
src/features/snippets/services/build-client-snippets.client.ts (1)
  • buildClientSnippets (4-15)
src/features/game/state/game-store.ts (3)
  • snippets (71-77)
  • newSnippet (102-113)
  • language (55-70)
🔇 Additional comments (7)
package.json (1)

43-43: Sonner dependency addition matches usage

Adding "sonner": "^2.0.7" here is consistent with the new Toaster / toast imports in the game layout and hooks; no issues from the package.json side.

src/features/shared/types/snippet.ts (1)

5-9: Extending IRawSnippet with id is consistent with new flows

Adding id: string to IRawSnippet matches the new snippet-by-id and sharing logic. Just ensure all places constructing IRawSnippet (not only the updated services in this PR) now populate id so type checks stay green.

src/features/snippets/services/get-snippets.server.ts (1)

3-3: Server snippet getters correctly align with new IRawSnippet shape

Including id in getRandomSnippets’ return objects and introducing getSnippetById (with languageId and computed disabledRanges) cleanly align the server layer with the updated IRawSnippet contract and the new snippet-by-id flow. Both functions look correct and consistent.

Also applies to: 20-23, 38-51

src/app/game/page.tsx (1)

28-30: URL-based snippet activation wiring looks correct

Using useSearchParams to read snippetIdFromUrl and the new activateLanguageWithSnippetEvent in the init effect gives you a clean “if snippet param → start from that snippet, else → normal init” flow. The dependency array [availableLanguages, snippetIdFromUrl] also means that if the query param is added or removed client-side, the game re-initializes accordingly. No issues from a correctness standpoint.

Also applies to: 44-45, 83-105

src/features/game/components/game-view.tsx (2)

10-10: LGTM!

The new imports support the share functionality and toast notifications appropriately.

Also applies to: 12-12


72-95: LGTM!

The UI layout restructuring to accommodate both share and settings buttons is well-implemented. The grid layout approach properly centers content while allowing for the button cluster, and the disabled states are appropriately synchronized with game status.

src/server/trpc/routers/snippet.ts (1)

4-4: LGTM!

The addition of getSnippetById to the imports appropriately supports the new endpoint.

@mattiacerutti mattiacerutti merged commit 52ee91f into main Nov 23, 2025
3 of 4 checks passed
@mattiacerutti mattiacerutti deleted the feat/share-snippet branch November 23, 2025 18:49
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.

1 participant