Skip to content

feat: add folder organization for feature flags (#271)#345

Open
promisingcoder wants to merge 1 commit intodatabuddy-analytics:mainfrom
promisingcoder:feat/flag-folders-271
Open

feat: add folder organization for feature flags (#271)#345
promisingcoder wants to merge 1 commit intodatabuddy-analytics:mainfrom
promisingcoder:feat/flag-folders-271

Conversation

@promisingcoder
Copy link

Description

Adds a folder system to organize feature flags in the dashboard UI, using Option A (simple string field) as recommended.

/claim #271

Changes (7 files, 292 lines)

Database

  • packages/db/src/drizzle/schema.ts — Added optional folder text column to flags table + composite index on (websiteId, folder)

Shared Schema

  • packages/shared/src/flags/index.ts — Added folder to flagFormSchema

RPC

  • packages/rpc/src/routers/flags.ts:
    • flags.list: Added optional folder filter parameter + cache key integration
    • flags.create: Passes folder to insert
    • flags.update: Added folder to update schema

Dashboard UI

  • Folder grouping (flags-list.tsx): FolderSection component with collapsible headers (Phosphor folder icons, flag count, caret toggle). Groups alphabetically with "Uncategorized" last. Falls back to flat list when no folders exist.
  • Folder selector (flag-sheet.tsx): Autocomplete showing existing folders as suggestions, inline "Create" option for new folders, chip display with remove button.
  • Types (types.ts): Added folder to Flag interface and existingFolders to FlagSheetProps.
  • Page (page.tsx): Extracts existing folders from flags and passes to FlagSheet.

Design Decisions

  • Option A (string field) — simple, backward compatible, no migration headaches
  • Flat list preserved when no flags use folders (zero visual change for existing users)
  • Follows existing UI patterns: Radix Collapsible, Phosphor icons, existing component library
  • Mobile responsive — folder sections stack naturally

)

Add optional folder field to feature flags for grouping and organization.
Flags can be assigned to folders via the create/edit sheet, and the list
view automatically groups flags by folder with collapsible sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 15, 2026

@promisingcoder is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c6e2029-5846-436b-8389-2622322da624

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Tip

CodeRabbit can use oxc to improve the quality of JavaScript and TypeScript code reviews.

Add a configuration file to your project to customize how CodeRabbit runs oxc.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 15, 2026

Greptile Summary

Adds a folder organization system for feature flags using a simple string field (Option A). The change spans the full stack: a new folder text column + composite index in the DB schema, folder filtering/persistence in the RPC layer, a folder autocomplete selector in the flag creation/edit sheet, and collapsible folder grouping in the flags list UI.

  • Database: New nullable folder column on flags table with a composite (websiteId, folder) index for efficient filtering
  • RPC: flags.list supports optional folder filtering with cache key integration; flags.create and flags.update pass folder through correctly
  • UI: FolderSection component groups flags by folder with collapsible headers; falls back to flat list when no folders are in use (zero visual change for existing users)
  • Folder selector: Autocomplete with existing folder suggestions and inline "Create new" option
  • Minor style suggestions around setTimeout-based blur handling and || vs ?? for nullish coercion in the RPC layer

Confidence Score: 4/5

  • This PR is safe to merge — it adds a backward-compatible optional field with no breaking changes to existing behavior.
  • The changes are well-structured across all layers, follow existing patterns (nullable optional text column, permissive output schema), and gracefully fall back to the existing flat list when no folders are used. Minor style suggestions around blur handling and nullish coercion are non-blocking.
  • packages/rpc/src/routers/flags.ts — minor || vs ?? coercion concern in create handler

Important Files Changed

Filename Overview
packages/db/src/drizzle/schema.ts Added optional folder text column and composite index (websiteId, folder) to flags table. Clean addition with proper NULL handling.
packages/shared/src/flags/index.ts Added folder: z.string().nullable().optional() to flagFormSchema. Consistent with existing environment field pattern.
packages/rpc/src/routers/flags.ts Added folder filter to list, folder pass-through in create/update. Cache key updated correctly. Minor concern with `
apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-sheet.tsx New folder autocomplete selector with create/search functionality. Clean integration with form. Dropdown lacks keyboard navigation support.
apps/dashboard/app/(main)/websites/[id]/flags/_components/flags-list.tsx New FolderSection collapsible component with folder grouping logic. Falls back to flat list when no folders exist. Well-structured.
apps/dashboard/app/(main)/websites/[id]/flags/_components/types.ts Added folder to Flag interface and existingFolders to FlagSheetProps. Straightforward type additions.
apps/dashboard/app/(main)/websites/[id]/flags/page.tsx Extracts existing folder names from active flags via useMemo and passes to FlagSheet. Clean data flow.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[FlagsPage] -->|extracts existingFolders| B[FlagSheet]
    B -->|folder autocomplete| C{User selects folder?}
    C -->|Yes: existing| D[field.onChange folder]
    C -->|Yes: new| E[field.onChange trimmed input]
    C -->|No / removes| F[field.onChange null]
    D --> G[form submit]
    E --> G
    F --> G
    G -->|create| H[RPC flags.create]
    G -->|update| I[RPC flags.update]
    H -->|folder stored| J[(DB: flags.folder)]
    I -->|folder stored| J
    J -->|query flags.list| K[FlagsList]
    K --> L{Any flag has folder?}
    L -->|Yes| M[FolderSection grouped view]
    L -->|No| N[Flat list unchanged]
Loading

Last reviewed commit: ad29f6f

Comment on lines +614 to +619
onBlur={() => {
// Delay to allow click on suggestions
setTimeout(
() => setShowFolderSuggestions(false),
200
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: setTimeout for blur is fragile

Using setTimeout(() => setShowFolderSuggestions(false), 200) to keep the dropdown open long enough for click events to register is a common but fragile pattern — it's a race condition that can fail on slow devices or under heavy CPU load. Consider using onMouseDown with e.preventDefault() on the suggestion buttons instead, which prevents the blur from firing in the first place. That said, this pattern is used elsewhere in the codebase, so this is non-blocking.

websiteId: input.websiteId || null,
organizationId: input.organizationId || null,
environment: input.environment || existingFlag?.[0]?.environment,
folder: input.folder || null,
Copy link
Contributor

Choose a reason for hiding this comment

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

|| coerces empty string to null

input.folder || null will treat an empty string "" as falsy and convert it to null. While the client currently trims and nullifies empty strings before sending, the createFlagSchema (which spreads flagFormSchema.shape) does allow z.string() — meaning an empty string "" could pass validation. Using input.folder ?? null would be more precise and only coerce undefined/null, matching the pattern you'd want for "no folder". The same applies to the restore path on line 637.

Suggested change
folder: input.folder || null,
folder: input.folder ?? null,

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants