Skip to content

feat: Implement retrospective template system#40

Merged
TheEagleByte merged 7 commits intomainfrom
issue-13-template-system
Sep 27, 2025
Merged

feat: Implement retrospective template system#40
TheEagleByte merged 7 commits intomainfrom
issue-13-template-system

Conversation

@TheEagleByte
Copy link
Copy Markdown
Owner

@TheEagleByte TheEagleByte commented Sep 27, 2025

Summary

  • ✅ Added 7 retrospective templates including DAKI template (previously only had 6)
  • ✅ Implemented database schema for custom templates with proper RLS policies
  • ✅ Created UI components for template selection and custom template creation
  • ✅ Added support for saving template preferences

What's Changed

Templates Added

  • DAKI (Drop, Add, Keep, Improve) - New template for process improvement
  • All existing templates now have proper icon support

Database Changes

  • Created custom_templates table for user-created templates
  • Added preferred_template_id and template_preferences columns to profiles table
  • Implemented RLS policies for secure template access

UI Components

  • TemplateSelector - Component for selecting from built-in and custom templates
  • CustomTemplateCreator - Drag-and-drop interface for creating custom templates
  • Templates can be public (organization-wide) or team-specific

Features

  • Users can save their preferred template
  • Recent templates are tracked and prioritized in the selector
  • Custom templates support up to 6 columns with customizable colors and icons
  • Full TypeScript type safety with generated Supabase types

Test Plan

  • Verify all 7 templates display correctly in the selector
  • Test custom template creation with drag-and-drop reordering
  • Verify template preferences are saved to user profile
  • Test RLS policies for template visibility
  • Ensure build passes without errors

Fixes #13

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • UI to create and manage custom board templates with save/cancel, public toggle, and up to 6 draggable columns; template picker includes a dialog to create templates.
  • Enhancements

    • Added "DAKI" template, expanded column icon set, and improved template sorting (preferred → recent → A–Z).
  • Chores

    • Database additions for custom templates, profile preference fields, indexes, RLS policies, and update-timestamp trigger.

- Add DAKI template to existing template collection
- Create database schema for custom templates with RLS policies
- Build TemplateSelector UI component for template selection
- Implement CustomTemplateCreator for creating custom templates
- Add hooks for managing templates and preferences
- Update TypeScript types from Supabase schema
- Enable template preferences saving to user profile

Fixes #13
Copilot AI review requested due to automatic review settings September 27, 2025 16:28
@vercel
Copy link
Copy Markdown

vercel bot commented Sep 27, 2025

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

Project Deployment Preview Comments Updated (UTC)
scrumkit Ready Ready Preview Comment Sep 27, 2025 10:27pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 27, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a template system: new client components for creating/selecting templates, Supabase-backed template hooks and preference APIs, a new DAKI board template, expanded Supabase types including custom_templates, and a DB migration creating custom_templates with indexes, RLS policies, and profile preference fields.

Changes

Cohort / File(s) Summary
Components — Creator
src/components/CustomTemplateCreator.tsx
New client React component CustomTemplateCreator: manages template name/description/isPublic and columns; supports add/update/delete (max 6), per-column title/description/color/icon editing, drag-and-drop reordering (DnD Kit), validation, and onSave/onCancel; accepts initialColumns.
Components — Selector
src/components/TemplateSelector.tsx
New client React component TemplateSelector: merges built-in boardTemplates with user customTemplates, sorts by preferred→recent→name, renders selectable list with badges and per-column badges, optional "Create Custom Template" dialog invoking onCreateCustomTemplate; exports TemplateSelector and TemplateSelectorProps.
Components — Icons / Board UI
src/components/RetrospectiveBoard.tsx
Extended lucide-react imports and column→icon mapping with additional icons and adjusted sentiment mappings.
Hooks / API
src/hooks/use-templates.ts
New Supabase-backed hooks and helpers: useTemplates, useCreateCustomTemplate, useUpdateCustomTemplate, useDeleteCustomTemplate, useTemplatePreferences, and createRetrospectiveFromTemplate — provide fetch/insert/update/delete, preference persistence, normalization, and loading/error states.
Templates data
src/lib/boards/templates.ts
Added new public board template daki (columns: drop, add, keep, improve).
Supabase types
src/lib/supabase/types.ts
Major type changes: added custom_templates table types and relationships; added preferred_template_id and template_preferences to profiles and organizations; introduced __InternalSupabase, DatabaseWithoutInternals, DefaultSchema, schema-aware generics (Tables, TablesInsert, TablesUpdate, Enums, CompositeTypes) and exported Constants.
Database migration
supabase/migrations/20250927_add_custom_templates.sql
Adds public.custom_templates (UUID PK, FKs, columns jsonb, is_public, timestamps), updates public.profiles with preference fields, adds indexes, enables RLS and policies for select/insert/update/delete, and trigger to auto-update updated_at.
Tests
src/lib/boards/__tests__/templates.test.ts
Expanded test data: extended valid lucide icon names with 'X', 'Check', and 'Plus'.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Creator as CustomTemplateCreator (UI)
  participant HooksCreate as useCreateCustomTemplate
  participant DB as Supabase (custom_templates)

  User->>Creator: Edit template (name, visibility, columns)
  Creator->>User: validate (name & column titles)
  User->>Creator: Click "Save"
  Creator->>HooksCreate: createTemplate({ name, description, is_public, columns, team/org })
  HooksCreate->>DB: INSERT into public.custom_templates
  DB-->>HooksCreate: created record / error
  HooksCreate-->>Creator: result
  alt success
    Creator-->>User: close dialog / confirm saved
  else error
    Creator-->>User: show error
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Selector as TemplateSelector (UI)
  participant HooksList as useTemplates
  participant Prefs as useTemplatePreferences
  participant Data as boardTemplates + custom_templates

  User->>Selector: Open selector
  Selector->>HooksList: fetch custom templates (team/org/public)
  Selector->>Prefs: fetch user preferences
  HooksList-->>Selector: customTemplates
  Prefs-->>Selector: preferences
  Selector->>Data: merge templates
  Selector->>Selector: sort (preferred → recent → name)
  Selector-->>User: render selectable list
  User->>Selector: choose template
  Selector-->>User: onTemplateSelect(templateId)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

I twitch my whiskers, nudge a card,
Columns hop and line the yard.
Colors, icons, drag and save,
DAKI sails from cave to wave.
A rabbit stamps — templates brave! 🐇✨

Pre-merge checks and finishing touches

❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: Implement retrospective template system” accurately and concisely summarizes the primary change of this pull request, which is the introduction of a comprehensive template system for retrospectives including built-in templates, custom template creation, and associated database and UI enhancements. It uses clear, conventional commit syntax without extraneous details, making it easy for collaborators to understand the main purpose at a glance.
Linked Issues Check ✅ Passed This pull request fully addresses the objectives of linked issue #13 by defining the template data structure and adding at least five core templates (including Mad/Sad/Glad; Start/Stop/Continue; 4Ls; Sailboat; DAKI), implementing the TemplateSelector UI, providing CustomTemplateCreator for user-defined templates, persisting user preferences via profile updates, and securing access with RLS policies. All acceptance criteria—template availability, selection UI, custom creation, and preference saving—are met as evidenced by the new components, hooks, migrations, and tests.
Out of Scope Changes Check ✅ Passed All modifications in this pull request—including UI components for template selection and creation, built-in template definitions, icon mappings, TypeScript types, Supabase migrations, RLS policies, hooks, and test updates—are directly related to implementing the retrospective template system defined in issue #13, and there are no unrelated or extraneous changes.

📜 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 69f7b84 and eb407ae.

📒 Files selected for processing (1)
  • src/components/RetrospectiveBoard.tsx (2 hunks)

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a comprehensive retrospective template system with custom template support and user preferences. It adds database schema for custom templates, introduces the DAKI retrospective template, and creates UI components for template management.

  • Added DAKI template and expanded icon support for all retrospective column types
  • Implemented database schema with custom_templates table and user template preferences
  • Created TemplateSelector and CustomTemplateCreator components for template management

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
supabase/migrations/20250927_add_custom_templates.sql Database migration adding custom_templates table and template preferences to profiles
src/lib/supabase/types.ts Updated TypeScript types with new database schema and improved type generation
src/lib/boards/templates.ts Added DAKI template with Drop, Add, Keep, Improve columns
src/hooks/use-templates.ts Custom hooks for template management and user preferences
src/components/TemplateSelector.tsx Component for selecting built-in and custom templates with preference sorting
src/components/RetrospectiveBoard.tsx Added icon mappings for new template column types
src/components/CustomTemplateCreator.tsx Drag-and-drop interface for creating custom templates

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/RetrospectiveBoard.tsx (2)

84-112: Honor custom column.icon when present and fix icon mismatches (mad/sad/longed-for)

Currently icons are chosen only by column_type, so custom template icons are ignored. Also, mappings for mad/sad/longed-for don’t match templates.ts. Use column.icon first, fall back to type.

Apply this diff:

-// Icon mapping for different column types
-const getColumnIcon = (columnType: string): React.ReactNode => {
-  const iconMap: Record<string, React.ReactNode> = {
+// Icon mapping for different column types, with override via column.icon
+const getColumnIcon = (column: { column_type: string; icon?: string }): React.ReactNode => {
+  // map string icon names (used by CustomTemplateCreator) to components
+  const iconByName: Record<string, React.ReactNode> = {
+    ThumbsUp: <ThumbsUp className="h-5 w-5" />,
+    ThumbsDown: <ThumbsDown className="h-5 w-5" />,
+    Heart: <Heart className="h-5 w-5" />,
+    Star: <Star className="h-5 w-5" />,
+    Lightbulb: <Lightbulb className="h-5 w-5" />,
+    AlertTriangle: <AlertTriangle className="h-5 w-5" />,
+    Target: <Target className="h-5 w-5" />,
+    TrendingUp: <TrendingUp className="h-5 w-5" />,
+    TrendingDown: <TrendingDown className="h-5 w-5" />,
+    MessageSquare: <MessageSquare className="h-5 w-5" />,
+    Check: <Check className="h-5 w-5" />,
+    X: <X className="h-5 w-5" />,
+    Plus: <Plus className="h-5 w-5" />,
+    PlayCircle: <PlayCircle className="h-5 w-5" />,
+    PauseCircle: <PauseCircle className="h-5 w-5" />,
+    Smile: <Smile className="h-5 w-5" />,
+    Frown: <Frown className="h-5 w-5" />,
+    Meh: <Meh className="h-5 w-5" />,
+  };
+
+  // Use explicit icon if provided (custom templates)
+  if (column.icon && iconByName[column.icon]) {
+    return iconByName[column.icon];
+  }
+
+  const iconMap: Record<string, React.ReactNode> = {
     'went-well': <ThumbsUp className="h-5 w-5" />,
     'improve': <Lightbulb className="h-5 w-5" />,
     'blockers': <AlertTriangle className="h-5 w-5" />,
     'action-items': <Target className="h-5 w-5" />,
     'glad': <Smile className="h-5 w-5" />,
-    'sad': <Frown className="h-5 w-5" />,
-    'mad': <AlertTriangle className="h-5 w-5" />,
+    'sad': <Meh className="h-5 w-5" />,
+    'mad': <Frown className="h-5 w-5" />,
     'liked': <Heart className="h-5 w-5" />,
     'learned': <Lightbulb className="h-5 w-5" />,
     'lacked': <AlertTriangle className="h-5 w-5" />,
-    'longed-for': <Target className="h-5 w-5" />,
+    'longed-for': <Star className="h-5 w-5" />,
     'drop': <X className="h-5 w-5" />,
     'add': <Plus className="h-5 w-5" />,
     'keep': <Check className="h-5 w-5" />,
     'start': <PlayCircle className="h-5 w-5" />,
     'stop': <PauseCircle className="h-5 w-5" />,
     'continue': <TrendingUp className="h-5 w-5" />,
     'wind': <TrendingUp className="h-5 w-5" />,
     'anchor': <TrendingDown className="h-5 w-5" />,
     'rocks': <AlertTriangle className="h-5 w-5" />,
     'island': <Target className="h-5 w-5" />,
     'plus': <ThumbsUp className="h-5 w-5" />,
     'delta': <TrendingUp className="h-5 w-5" />,
   };
-  return iconMap[columnType] || <MessageSquare className="h-5 w-5" />;
+  return iconMap[column.column_type] || <MessageSquare className="h-5 w-5" />;
 };

As per coding guidelines.


646-647: Pass the full column to getColumnIcon after signature change

Wire the new helper that honors column.icon.

Apply this diff:

-                  {getColumnIcon(column.column_type)}
+                  {getColumnIcon(column)}
🧹 Nitpick comments (5)
src/components/RetrospectiveBoard.tsx (1)

405-410: Simplify intra-column insert index logic (comment vs. code mismatch)

Both branches return overIndex; remove the dead conditional.

Apply this diff:

-        // If dragging down, insert after; if dragging up, insert before
-        newPosition = activeIndex < overIndex ? overIndex : overIndex;
+        // Insert at the dropped index
+        newPosition = overIndex;
supabase/migrations/20250927_add_custom_templates.sql (1)

2-13: Add minimal schema validations for columns JSON and consider NOT NULLs

Enforce 1–6 columns and JSON array shape; optionally require created_by/organization_id.

Apply this diff:

 CREATE TABLE IF NOT EXISTS public.custom_templates (
   id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
   name text NOT NULL,
   description text,
-  created_by uuid REFERENCES public.profiles(id) ON DELETE CASCADE,
-  organization_id uuid REFERENCES public.organizations(id) ON DELETE CASCADE,
+  created_by uuid REFERENCES public.profiles(id) ON DELETE CASCADE,
+  organization_id uuid REFERENCES public.organizations(id) ON DELETE CASCADE,
   team_id uuid REFERENCES public.teams(id) ON DELETE CASCADE,
   is_public boolean DEFAULT false,
   columns jsonb NOT NULL,
   created_at timestamptz DEFAULT now(),
   updated_at timestamptz DEFAULT now()
 );
+
+-- Enforce array with 1..6 entries
+ALTER TABLE public.custom_templates
+  ADD CONSTRAINT custom_templates_columns_is_array CHECK (jsonb_typeof(columns) = 'array'),
+  ADD CONSTRAINT custom_templates_columns_len CHECK (jsonb_array_length(columns) BETWEEN 1 AND 6);
+
+-- Optional hardening (uncomment if desired):
+-- ALTER TABLE public.custom_templates ALTER COLUMN created_by SET NOT NULL;
+-- ALTER TABLE public.custom_templates ALTER COLUMN organization_id SET NOT NULL;
src/components/CustomTemplateCreator.tsx (1)

297-304: Guard against >6 columns on save (initialColumns can bypass UI limit)

Enforce the cap at submission.

Apply this diff:

   const handleSave = () => {
-    onSave({
+    const capped = columns.slice(0, 6);
+    onSave({
       name,
       description,
-      columns,
+      columns: capped,
       is_public: isPublic,
     });
   };
src/components/TemplateSelector.tsx (2)

62-80: Sorting mutates source array; memoize to avoid re-sorting every render

Optional: wrap in useMemo for stability (inputs: allTemplates, preferredTemplate, recentTemplates).

If desired, I can provide a small memoized snippet.


91-146: Wire “Create Custom Template” action or hide the button

Dialog currently closes with TODO. Either call onCreateCustomTemplate with a minimal payload or omit the button.

Proposed minimal call (pseudo, adjust to your creator contract):

-                  onClick={() => {
-                    // TODO: Implement custom template creation
-                    setIsCreateDialogOpen(false);
-                  }}
+                  onClick={() => {
+                    onCreateCustomTemplate?.({
+                      name: customTemplateName.trim(),
+                      description: customTemplateDescription.trim(),
+                      is_public: false,
+                      columns: [], // or current board columns if available in scope
+                      created_by: null,
+                      organization_id: null,
+                      team_id: null,
+                    } as any);
+                    setIsCreateDialogOpen(false);
+                  }}

If you want, I can adapt this to your exact hook signature once you confirm where team/org ids come from in this flow.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1322358 and 1d3f9bc.

📒 Files selected for processing (7)
  • src/components/CustomTemplateCreator.tsx (1 hunks)
  • src/components/RetrospectiveBoard.tsx (2 hunks)
  • src/components/TemplateSelector.tsx (1 hunks)
  • src/hooks/use-templates.ts (1 hunks)
  • src/lib/boards/templates.ts (1 hunks)
  • src/lib/supabase/types.ts (6 hunks)
  • supabase/migrations/20250927_add_custom_templates.sql (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use the @/* path alias for imports that target ./src/*
Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Files:

  • src/lib/boards/templates.ts
  • src/lib/supabase/types.ts
  • src/components/CustomTemplateCreator.tsx
  • src/hooks/use-templates.ts
  • src/components/TemplateSelector.tsx
  • src/components/RetrospectiveBoard.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{ts,tsx}: In client-side code, import the Supabase browser client from src/lib/supabase/client.ts
In server-side code, import the Supabase server client from src/lib/supabase/server.ts

Files:

  • src/lib/boards/templates.ts
  • src/lib/supabase/types.ts
  • src/components/CustomTemplateCreator.tsx
  • src/hooks/use-templates.ts
  • src/components/TemplateSelector.tsx
  • src/components/RetrospectiveBoard.tsx
src/components/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Add the "use client" directive to components that require client-side interactivity

Files:

  • src/components/CustomTemplateCreator.tsx
  • src/components/TemplateSelector.tsx
  • src/components/RetrospectiveBoard.tsx
src/components/RetrospectiveBoard.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Keep the main retrospective board component at src/components/RetrospectiveBoard.tsx implementing the four-column layout, real-time CRUD, voting, and author attribution

Files:

  • src/components/RetrospectiveBoard.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to **/*.{ts,tsx} : Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Applied to files:

  • src/lib/supabase/types.ts
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to src/components/RetrospectiveBoard.tsx : Keep the main retrospective board component at src/components/RetrospectiveBoard.tsx implementing the four-column layout, real-time CRUD, voting, and author attribution

Applied to files:

  • src/components/CustomTemplateCreator.tsx
  • src/components/TemplateSelector.tsx
  • src/components/RetrospectiveBoard.tsx
🧬 Code graph analysis (3)
src/components/CustomTemplateCreator.tsx (1)
src/lib/boards/templates.ts (1)
  • BoardColumn (2-9)
src/hooks/use-templates.ts (2)
src/lib/supabase/types.ts (2)
  • Tables (549-576)
  • TablesInsert (578-601)
src/lib/boards/templates.ts (2)
  • BoardTemplate (11-16)
  • BoardColumn (2-9)
src/components/TemplateSelector.tsx (2)
src/lib/supabase/types.ts (1)
  • Tables (549-576)
src/lib/boards/templates.ts (2)
  • boardTemplates (18-260)
  • BoardTemplate (11-16)
🪛 GitHub Check: Run Tests (20.x)
src/components/TemplateSelector.tsx

[failure] 159-159:
Unexpected any. Specify a different type


[failure] 158-158:
Unexpected any. Specify a different type

🪛 GitHub Actions: Tests
src/components/TemplateSelector.tsx

[error] 158-158: ESLint error: Unexpected any. Specify a different type. (no-explicit-any)

🔇 Additional comments (3)
src/lib/boards/templates.ts (1)

221-259: DAKI template addition looks good

Fields align with BoardColumn/BoardTemplate. No structural issues spotted.

Please verify icon rendering stays consistent in RetrospectiveBoard for:

  • drop → X
  • add → Plus
  • keep → Check
  • improve → TrendingUp
src/components/TemplateSelector.tsx (2)

23-24: Import path OK and types usage aligns with guidelines

Using Tables<> from generated Supabase types is correct here.


158-159: Install dependencies and verify ESLint passes
We couldn’t run eslint in the sandbox due to missing node_modules. Please run pnpm install (or npm install/yarn install) and then pnpm lint (or npm run lint/yarn lint) to confirm no Unexpected any errors remain.

- Fix loading state reset in use-templates hook to prevent stale errors
- Implement proper template creation handler in TemplateSelector
- Strengthen RLS policies to prevent cross-tenant data leaks
- Ensure organization_id constraints in INSERT and UPDATE policies
- Replace inline template creation with informational dialog
- Direct users to create templates from existing boards
- Remove unused form fields and imports
- Templates should be created with proper board context and user auth
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 (3)
src/components/TemplateSelector.tsx (2)

66-80: Avoid mutating the source when sorting

Sorting in place can cause subtle bugs if allTemplates is reused.

Apply:

-  const sortedTemplates = allTemplates.sort((a, b) => {
+  const sortedTemplates = [...allTemplates].sort((a, b) => {

232-240: Prefer stable keys over index for columns

Use a semantic key to reduce reconciliation issues.

Apply:

-                      {template.columns.map((column, index) => (
+                      {template.columns.map((column, index) => (
                         <Badge
-                          key={index}
+                          key={column.column_type ?? String(index)}
                           variant="secondary"
                           className="text-xs"
                         >
                           {column.title}
                         </Badge>
                       ))}
supabase/migrations/20250927_add_custom_templates.sql (1)

20-25: Indexing advice for common filters

Likely queries: by team_id, by organization_id with is_public, recents by created_at. Add composite indexes to support these.

Apply:

 CREATE INDEX IF NOT EXISTS idx_custom_templates_created_by ON public.custom_templates(created_by);
 CREATE INDEX IF NOT EXISTS idx_custom_templates_organization_id ON public.custom_templates(organization_id);
 CREATE INDEX IF NOT EXISTS idx_custom_templates_team_id ON public.custom_templates(team_id);
 CREATE INDEX IF NOT EXISTS idx_custom_templates_is_public ON public.custom_templates(is_public);
+CREATE INDEX IF NOT EXISTS idx_custom_templates_org_public ON public.custom_templates(organization_id, is_public);
+CREATE INDEX IF NOT EXISTS idx_custom_templates_team_created_at ON public.custom_templates(team_id, created_at DESC);

Also applies to: 33-51

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d3f9bc and afeca61.

📒 Files selected for processing (3)
  • src/components/TemplateSelector.tsx (1 hunks)
  • src/hooks/use-templates.ts (1 hunks)
  • supabase/migrations/20250927_add_custom_templates.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/use-templates.ts
🧰 Additional context used
📓 Path-based instructions (3)
src/components/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Add the "use client" directive to components that require client-side interactivity

Files:

  • src/components/TemplateSelector.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use the @/* path alias for imports that target ./src/*
Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Files:

  • src/components/TemplateSelector.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{ts,tsx}: In client-side code, import the Supabase browser client from src/lib/supabase/client.ts
In server-side code, import the Supabase server client from src/lib/supabase/server.ts

Files:

  • src/components/TemplateSelector.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Enable and rely on TypeScript strict mode

Applied to files:

  • src/components/TemplateSelector.tsx
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to src/components/RetrospectiveBoard.tsx : Keep the main retrospective board component at src/components/RetrospectiveBoard.tsx implementing the four-column layout, real-time CRUD, voting, and author attribution

Applied to files:

  • src/components/TemplateSelector.tsx
🧬 Code graph analysis (1)
src/components/TemplateSelector.tsx (2)
src/lib/supabase/types.ts (1)
  • Tables (549-576)
src/lib/boards/templates.ts (2)
  • boardTemplates (18-260)
  • BoardTemplate (11-16)
🪛 GitHub Check: Run Tests (20.x)
src/components/TemplateSelector.tsx

[failure] 180-180:
Unexpected any. Specify a different type


[failure] 179-179:
Unexpected any. Specify a different type


[failure] 148-148:
Unexpected any. Specify a different type

🪛 GitHub Actions: Tests
src/components/TemplateSelector.tsx

[error] 148-148: Unexpected any. Specify a different type @typescript-eslint/no-explicit-any

🔇 Additional comments (5)
src/components/TemplateSelector.tsx (3)

1-1: Good: client component directive present

"use client" is correctly added for an interactive component.


49-60: Type the unified list and safely deserialize JSON columns

Casts from unknown to BoardTemplate["columns"] hide shape errors and caused downstream any usage. Define a small union and a deserializer to normalize DB JSON to the UI shape.

Apply:

+type UnifiedTemplate = BoardTemplate & {
+  isCustom?: boolean;
+  isPublic?: boolean;
+  createdBy?: string | null;
+};
+
+type DbBoardColumn = {
+  column_type: string;
+  title: string;
+  description?: string | null;
+  color?: string | null;
+  icon?: string | null;
+  display_order?: number | null;
+};
+
+const deserializeColumns = (val: unknown): BoardTemplate["columns"] => {
+  if (!Array.isArray(val)) return [];
+  return (val as DbBoardColumn[]).map((c, i) => ({
+    column_type: c?.column_type ?? `column-${i}`,
+    title: c?.title ?? "",
+    description: c?.description ?? "",
+    color: c?.color ?? "bg-green-500/10 border-green-500/20",
+    icon: c?.icon ?? undefined,
+    display_order: typeof c?.display_order === "number" ? c.display_order : i,
+  }));
+};
+
-  const allTemplates = [
+  const allTemplates: UnifiedTemplate[] = [
     ...boardTemplates,
     ...customTemplates.map((ct) => ({
       id: ct.id,
       name: ct.name,
       description: ct.description || "",
-      columns: (ct.columns as unknown) as BoardTemplate["columns"],
+      columns: deserializeColumns(ct.columns as unknown),
       isCustom: true,
-      isPublic: ct.is_public,
-      createdBy: ct.created_by,
+      isPublic: !!ct.is_public,
+      createdBy: ct.created_by ?? null,
     })),
   ];

As per coding guidelines.

Also applies to: 23-24


176-181: Fix ESLint “no-explicit-any”: remove any-casts in badges

Template is a UnifiedTemplate; no cast needed.

Apply:

-            const isCustom = "isCustom" in template && (template as any).isCustom;
-            const isPublic = "isPublic" in template && (template as any).isPublic;
+            const isCustom = !!template.isCustom;
+            const isPublic = !!template.isPublic;
supabase/migrations/20250927_add_custom_templates.sql (2)

30-32: Restrict “public” visibility to the viewer’s organization(s)

Current policy leaks all is_public=true rows to every authenticated user; PR intent is org‑wide public, not global.

Apply:

-DROP POLICY IF EXISTS "Users can view public templates" ON public.custom_templates;
-CREATE POLICY "Users can view public templates" ON public.custom_templates
-  FOR SELECT USING (is_public = true);
+DROP POLICY IF EXISTS "Users can view public templates" ON public.custom_templates;
+CREATE POLICY "Users can view org-public templates" ON public.custom_templates
+  FOR SELECT USING (
+    is_public = true AND
+    organization_id IN (
+      SELECT t.organization_id
+      FROM public.teams t
+      WHERE t.id IN (
+        SELECT tm.team_id FROM public.team_members tm
+        WHERE tm.profile_id = auth.uid()
+      )
+    )
+  );

52-68: Enforce org/team consistency on INSERT

Rows can be written with mismatched organization_id vs team_id, or with NULL team_id (policy requires membership but NULL bypasses equality). Enforce consistency at policy time.

Apply:

-DROP POLICY IF EXISTS "Users can create templates for their teams" ON public.custom_templates;
-CREATE POLICY "Users can create templates for their teams" ON public.custom_templates
+DROP POLICY IF EXISTS "Users can create templates for their teams" ON public.custom_templates;
+CREATE POLICY "Users can create templates for their teams" ON public.custom_templates
   FOR INSERT WITH CHECK (
     created_by = auth.uid() AND
-    team_id IN (
+    team_id IS NOT NULL AND
+    team_id IN (
       SELECT team_id FROM public.team_members
       WHERE profile_id = auth.uid()
-    ) AND
-    (
-      organization_id IS NULL OR
-      organization_id IN (
-        SELECT t.organization_id
-        FROM public.teams t
-        JOIN public.team_members tm ON t.id = tm.team_id
-        WHERE tm.profile_id = auth.uid()
-      )
-    )
+    ) AND
+    organization_id = (SELECT t.organization_id FROM public.teams t WHERE t.id = team_id)
   );

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: 1

🧹 Nitpick comments (5)
src/components/TemplateSelector.tsx (5)

22-32: Narrow the create callback type to UI-collected fields

Passing near‑DB row shape from UI invites nulls for RLS‑checked fields. Accept only name/description here; let the caller enrich with auth/team/org before insert.

 interface TemplateSelectorProps {
   selectedTemplateId: string;
   onTemplateSelect: (templateId: string) => void;
   customTemplates?: Tables<"custom_templates">[];
   showCustomTemplateButton?: boolean;
-  onCreateCustomTemplate?: (template: Omit<Tables<"custom_templates">, "id" | "created_at" | "updated_at">) => void;
+  // UI gathers only basic metadata; server/hook adds auth/team/org/columns.
+  onCreateCustomTemplate?: (template: Pick<Tables<"custom_templates">, "name" | "description">) => void;
   userPreferences?: {
     preferred_template_id?: string | null;
     recent_templates?: string[];
   };
 }

As per coding guidelines (use generated Supabase types).


60-75: Avoid in-place sort (minor purity/readability)

Sorting in place mutates the local array. Copy before sort for clarity.

-  const sortedTemplates = allTemplates.sort((a, b) => {
+  const sortedTemplates = [...allTemplates].sort((a, b) => {

86-94: Button condition and label mismatch with behavior

The button shows only an info dialog; it doesn’t use the callback. Don’t gate it on onCreateCustomTemplate, and adjust the label to set expectations.

-        {showCustomTemplateButton && onCreateCustomTemplate && (
+        {showCustomTemplateButton && (
           <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
             <DialogTrigger asChild>
               <Button variant="outline" size="sm">
-                <Plus className="mr-2 h-4 w-4" />
-                Create Custom Template
+                <Plus aria-hidden="true" className="mr-2 h-4 w-4" />
+                How to create a custom template
               </Button>
             </DialogTrigger>

149-155: Accessibility: mark decorative icons aria-hidden

These icons convey no additional meaning; hide them from screen readers.

-                        <Layout className="h-4 w-4 text-muted-foreground" />
+                        <Layout aria-hidden="true" className="h-4 w-4 text-muted-foreground" />
-                            <Star className="mr-1 h-3 w-3" />
+                            <Star aria-hidden="true" className="mr-1 h-3 w-3" />
-                                <Globe className="mr-1 h-3 w-3" />
+                                <Globe aria-hidden="true" className="mr-1 h-3 w-3" />
-                                <Users className="mr-1 h-3 w-3" />
+                                <Users aria-hidden="true" className="mr-1 h-3 w-3" />
-                <Plus className="mr-2 h-4 w-4" />
+                <Plus aria-hidden="true" className="mr-2 h-4 w-4" />

As per accessibility best practices.

Also applies to: 168-174, 90-90


185-193: Use a stable key for columns

Avoid index keys to prevent remounts on reordering; column_type is stable.

-                      {template.columns.map((column, index) => (
+                      {template.columns.map((column) => (
                         <Badge
-                          key={index}
+                          key={column.column_type}
                           variant="secondary"
                           className="text-xs"
                         >
                           {column.title}
                         </Badge>
                       ))}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afeca61 and 07c5d64.

📒 Files selected for processing (1)
  • src/components/TemplateSelector.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/components/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Add the "use client" directive to components that require client-side interactivity

Files:

  • src/components/TemplateSelector.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use the @/* path alias for imports that target ./src/*
Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Files:

  • src/components/TemplateSelector.tsx
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{ts,tsx}: In client-side code, import the Supabase browser client from src/lib/supabase/client.ts
In server-side code, import the Supabase server client from src/lib/supabase/server.ts

Files:

  • src/components/TemplateSelector.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Enable and rely on TypeScript strict mode

Applied to files:

  • src/components/TemplateSelector.tsx
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to src/components/RetrospectiveBoard.tsx : Keep the main retrospective board component at src/components/RetrospectiveBoard.tsx implementing the four-column layout, real-time CRUD, voting, and author attribution

Applied to files:

  • src/components/TemplateSelector.tsx
🧬 Code graph analysis (1)
src/components/TemplateSelector.tsx (2)
src/lib/supabase/types.ts (1)
  • Tables (549-576)
src/lib/boards/templates.ts (2)
  • boardTemplates (18-260)
  • BoardTemplate (11-16)
🪛 GitHub Check: Run Tests (20.x)
src/components/TemplateSelector.tsx

[failure] 133-133:
Unexpected any. Specify a different type


[failure] 132-132:
Unexpected any. Specify a different type


[failure] 107-107:
" can be escaped with &quot;, &ldquo;, &#34;, &rdquo;


[failure] 107-107:
" can be escaped with &quot;, &ldquo;, &#34;, &rdquo;

🪛 GitHub Actions: Tests
src/components/TemplateSelector.tsx

[error] 107-107: " can be escaped with &quot;, &ldquo;, &#34;, &rdquo; react/no-unescaped-entities

🔇 Additional comments (3)
src/components/TemplateSelector.tsx (3)

1-1: Good: client component directive present

"use client" is correctly set for an interactive component.


44-55: Unify types and safely deserialize custom columns (remove unsafe casts)

The (ct.columns as unknown) as BoardTemplate["columns"] cast hides shape issues. Define a unified template type and a small deserializer for JSON columns.

   const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);

+  type UnifiedTemplate = BoardTemplate & {
+    isCustom?: boolean;
+    isPublic?: boolean;
+    createdBy?: string | null;
+  };
+
+  type DbBoardColumn = {
+    column_type?: string;
+    title?: string;
+    description?: string | null;
+    color?: string | null;
+    icon?: string | null;
+    display_order?: number | null;
+  };
+
+  const deserializeColumns = (val: unknown): BoardTemplate["columns"] => {
+    if (!Array.isArray(val)) return [];
+    return (val as DbBoardColumn[]).map((c, i) => ({
+      column_type: c?.column_type ?? `column-${i}`,
+      title: c?.title ?? "",
+      description: c?.description ?? "",
+      color: c?.color ?? "bg-green-500/10 border-green-500/20",
+      icon: c?.icon ?? undefined,
+      display_order: typeof c?.display_order === "number" ? c.display_order : i,
+    }));
+  };
+
-  const allTemplates = [
+  const allTemplates: UnifiedTemplate[] = [
     ...boardTemplates,
     ...customTemplates.map((ct) => ({
       id: ct.id,
       name: ct.name,
-      description: ct.description || "",
-      columns: (ct.columns as unknown) as BoardTemplate["columns"],
+      description: ct.description ?? "",
+      columns: deserializeColumns(ct.columns as unknown),
       isCustom: true,
       isPublic: ct.is_public,
       createdBy: ct.created_by,
     })),
   ];

Based on learnings.


132-134: Fix ESLint no-explicit-any by using the unified type

Remove (template as any) and rely on the optional flags from UnifiedTemplate.

-            const isCustom = "isCustom" in template && (template as any).isCustom;
-            const isPublic = "isPublic" in template && (template as any).isPublic;
+            const isCustom = !!template.isCustom;
+            const isPublic = !!template.isPublic;
#!/bin/bash
# Verify no remaining any-casts in this file
rg -n "as any" src/components/TemplateSelector.tsx || echo "No any-casts found"

- Fix unescaped quotes in TemplateSelector JSX
- Replace any types with proper type annotations
- Fix Json type usage in template preferences
- Ensure proper type casting for custom template columns
- Fix mad template icon: AlertTriangle -> Frown
- Fix sad template icon: Frown -> Meh
- Fix longed-for template icon: Target -> Star
- Add missing Meh and Star icon imports

Icons now match the template definitions in templates.ts
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07c5d64 and a66e9c9.

📒 Files selected for processing (2)
  • src/components/TemplateSelector.tsx (1 hunks)
  • src/hooks/use-templates.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/components/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Add the "use client" directive to components that require client-side interactivity

Files:

  • src/components/TemplateSelector.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use the @/* path alias for imports that target ./src/*
Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Files:

  • src/components/TemplateSelector.tsx
  • src/hooks/use-templates.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{ts,tsx}: In client-side code, import the Supabase browser client from src/lib/supabase/client.ts
In server-side code, import the Supabase server client from src/lib/supabase/server.ts

Files:

  • src/components/TemplateSelector.tsx
  • src/hooks/use-templates.ts
🧠 Learnings (3)
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Enable and rely on TypeScript strict mode

Applied to files:

  • src/components/TemplateSelector.tsx
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to src/components/RetrospectiveBoard.tsx : Keep the main retrospective board component at src/components/RetrospectiveBoard.tsx implementing the four-column layout, real-time CRUD, voting, and author attribution

Applied to files:

  • src/components/TemplateSelector.tsx
📚 Learning: 2025-09-27T16:07:41.169Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-27T16:07:41.169Z
Learning: Applies to **/*.{ts,tsx} : Use generated database types from src/lib/supabase/types.ts for Supabase-related TypeScript typings

Applied to files:

  • src/hooks/use-templates.ts
🧬 Code graph analysis (2)
src/components/TemplateSelector.tsx (2)
src/lib/supabase/types.ts (1)
  • Tables (549-576)
src/lib/boards/templates.ts (2)
  • boardTemplates (18-260)
  • BoardTemplate (11-16)
src/hooks/use-templates.ts (2)
src/lib/supabase/types.ts (3)
  • Tables (549-576)
  • TablesInsert (578-601)
  • Json (1-7)
src/lib/boards/templates.ts (2)
  • BoardTemplate (11-16)
  • BoardColumn (2-9)
🔇 Additional comments (1)
src/components/TemplateSelector.tsx (1)

44-55: Guard against malformed custom template columns before rendering

ct.columns comes back from Supabase as generic Json, yet we immediately coerce it into BoardTemplate["columns"] and later call .map. If a row ever carries null, an object, or otherwise malformed JSON (legacy data, manual insert, failed serialization), this cast will blow up the selector at runtime. Please funnel the payload through a small deserializer that verifies the shape and supplies sane fallbacks before rendering.

+const deserializeColumns = (value: Tables<'custom_templates'>['columns']): BoardTemplate['columns'] => {
+  if (!Array.isArray(value)) {
+    return [];
+  }
+
+  return value.map((column, index) => ({
+    column_type: typeof column?.column_type === "string" ? column.column_type : `column-${index}`,
+    title: typeof column?.title === "string" ? column.title : "",
+    description: typeof column?.description === "string" ? column.description ?? "" : "",
+    color: typeof column?.color === "string" ? column.color : "bg-green-500/10 border-green-500/20",
+    icon: typeof column?.icon === "string" ? column.icon : undefined,
+    display_order: typeof column?.display_order === "number" ? column.display_order : index,
+  }));
+};
...
-      columns: (ct.columns as unknown) as BoardTemplate["columns"],
+      columns: deserializeColumns(ct.columns),

- Add deserializeColumns function to safely handle malformed custom template data
- Guard against null, non-array, or invalid column data from database
- Add X, Check, and Plus icons to test's validIcons list for DAKI template
- Prevents runtime errors when custom templates have corrupted column data

Addresses CodeRabbit review feedback
@TheEagleByte TheEagleByte merged commit 1a34910 into main Sep 27, 2025
3 of 4 checks passed
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.

[EPIC-002] Story 6: Template System

2 participants