Skip to content

Comments

fix: 🎯 Bounty: Feature Flag Folders for Organization#322

Closed
makaiachildress-web wants to merge 1 commit intodatabuddy-analytics:mainfrom
makaiachildress-web:fix/issue-271-bounty
Closed

fix: 🎯 Bounty: Feature Flag Folders for Organization#322
makaiachildress-web wants to merge 1 commit intodatabuddy-analytics:mainfrom
makaiachildress-web:fix/issue-271-bounty

Conversation

@makaiachildress-web
Copy link

Summary\n\nFixes #271\n\n## Changes\n\nImplemented fix for the issue described above.\n\n/claim #271

)

Add a folder system to organize feature flags in the dashboard UI.
This is purely for UI organization and does not affect flag evaluation,
dependencies, API responses, or SDK behavior.

Changes:
- Add optional `folder` text field to flags table with index
- Extend list/create/update API endpoints with folder support
- Add folder validation (alphanumeric, hyphens, underscores, slashes, spaces)
- Create FolderSidebar component with tree view, create/rename/delete
- Add collapsible folder sections to FlagsList with flag counts
- Add folder selector with quick-pick chips to flag create/edit sheet
- Add Jotai atoms for folder selection and collapse state
- Add 49 tests for folder schema validation and API contracts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@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.


Makaia Taye Childress seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@vercel
Copy link

vercel bot commented Feb 18, 2026

Someone 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 Feb 18, 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.

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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@dosubot
Copy link

dosubot bot commented Feb 18, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Greptile Summary

Adds feature flag folder organization — flags can be assigned to hierarchical folder paths (e.g., billing/payments), displayed in a collapsible tree sidebar, and grouped in the main list view. Changes span the full stack: shared Zod schema, DB schema (new folder text column + index), RPC handlers (list/create/update with folder support), Jotai state atoms, and three new/modified dashboard components.

  • Schema & Validation: Folder field added as nullable/optional string with a 100-char max and alphanumeric+slashes regex, consistently applied across flagFormSchema, createFlagSchema, updateFlagSchema, and listFlagsSchema.
  • Backend: List handler supports folder filtering (including null/uncategorized via empty string). Create and update handlers persist the folder to the DB. Cache keys incorporate folder filter.
  • Frontend: New FolderSidebar with tree navigation, create/rename/delete operations. FlagsList conditionally renders folder-grouped sections. FlagSheet form includes a folder input with existing-folder quick-select.
  • Tests: Comprehensive schema validation tests for folder field across all CRUD schemas and path patterns.
  • Issues found: Folder deletion lacks a confirmation dialog for what is a destructive action, batch mutations (move/rename all flags in a folder) have no error handling, and the new folder create button is missing an aria-label.

Confidence Score: 3/5

  • Functional feature with good validation coverage, but batch operations lack error handling and folder deletion has no confirmation — both could cause silent data inconsistency or accidental bulk changes.
  • Score of 3 reflects solid schema validation, comprehensive tests, and clean DB integration, offset by the missing error handling on batch folder operations (move/rename), no confirmation dialog on destructive delete action, and a minor accessibility gap. The core feature works, but the edge cases around bulk operations need attention before merge.
  • apps/dashboard/app/(main)/websites/[id]/flags/page.tsx (batch mutation error handling), apps/dashboard/app/(main)/websites/[id]/flags/_components/folder-sidebar.tsx (delete confirmation, aria-label)

Important Files Changed

Filename Overview
apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-sheet.tsx Adds folder field to the flag create/edit form with existing-folder quick-select buttons. Clean integration.
apps/dashboard/app/(main)/websites/[id]/flags/_components/flags-list.tsx Adds FolderSection component for grouping flags by folder with collapsible sections. Animates height property which violates UI guidelines.
apps/dashboard/app/(main)/websites/[id]/flags/_components/folder-sidebar.tsx New folder sidebar component with tree structure, create/rename/delete operations. Missing aria-label on icon-only button, and delete folder lacks confirmation dialog. Has constant declaration between imports.
apps/dashboard/app/(main)/websites/[id]/flags/_components/types.ts Adds optional folder field to the Flag interface. Straightforward change.
apps/dashboard/app/(main)/websites/[id]/flags/page.tsx Integrates folder sidebar with main flags page. Batch mutations (move/rename folder) fire multiple .mutate() calls in a loop without error handling.
apps/dashboard/stores/jotai/flagsAtoms.ts Adds two new Jotai atoms for folder selection and collapsed state. Clean and simple.
packages/db/src/drizzle/schema.ts Adds folder text column and composite index on (websiteId, folder). No migration file included.
packages/rpc/src/routers/flags-folders.test.ts Comprehensive schema validation tests for folder field across create, update, list, and path validation scenarios.
packages/rpc/src/routers/flags.ts Adds folder support to list/create/update schemas and handlers. Uses `input.folder
packages/shared/src/flags/index.ts Adds folder field to flagFormSchema with proper validation (max 100 chars, alphanumeric+slashes regex, nullable, optional).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[FlagsPage] -->|flags list| B[FolderSidebar]
    A -->|filtered flags| C[FlagsList]
    A -->|flag edit/create| D[FlagSheet]

    B -->|selectedFolderAtom| E[Jotai Store]
    B -->|collapsedFoldersAtom| E
    C -->|collapsedFoldersAtom| E

    B -->|onMoveFlagsToRoot| F[updateFlagMutation]
    B -->|onRenameFolderFlags| F
    F -->|mutate folder: null| G[flags.update RPC]
    F -->|mutate folder: newPath| G

    D -->|create/update with folder| H[flags.create / flags.update RPC]
    G --> I[(DB: flags.folder column)]
    H --> I

    C -->|hasFolders?| J{Group by folder}
    J -->|yes| K[FolderSection per group]
    J -->|no| L[Flat FlagRow list]
    K --> M[FlagRow per flag]
Loading

Last reviewed commit: 6d4bffc

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

10 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +119 to +131
const handleMoveFlagsToRoot = useCallback(
(folderPath: string) => {
const flagsInFolder = activeFlags.filter(
(f) =>
(f as Flag).folder === folderPath ||
(f as Flag).folder?.startsWith(`${folderPath}/`)
);
for (const flag of flagsInFolder) {
updateFlagMutation.mutate({
id: flag.id,
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.

Batch mutations have no error handling

handleMoveFlagsToRoot calls updateFlagMutation.mutate() in a synchronous loop for every flag in the folder. If any individual mutation fails (e.g., network error, validation failure), there's no error handling — the failure is silently swallowed and some flags may be moved while others aren't, leaving the folder in a partial state. The same issue applies to handleRenameFolderFlags below.

Consider using mutateAsync with Promise.allSettled (or a sequential await loop) so failures can be surfaced to the user:

const handleMoveFlagsToRoot = useCallback(
    async (folderPath: string) => {
        const flagsInFolder = activeFlags.filter(
            (f) =>
                (f as Flag).folder === folderPath ||
                (f as Flag).folder?.startsWith(`${folderPath}/`)
        );
        const results = await Promise.allSettled(
            flagsInFolder.map((flag) =>
                updateFlagMutation.mutateAsync({ id: flag.id, folder: null })
            )
        );
        const failures = results.filter((r) => r.status === "rejected");
        if (failures.length > 0) {
            toast.error(`Failed to move ${failures.length} flag(s)`);
        }
    },
    [activeFlags, updateFlagMutation]
);

Comment on lines +319 to +328
const handleDeleteFolder = useCallback(
(path: string) => {
onMoveFlagsToRoot(path);
if (selectedFolder === path) {
setSelectedFolder(null);
}
toast.success(`Folder "${path}" deleted — flags moved to root`);
},
[onMoveFlagsToRoot, selectedFolder, setSelectedFolder]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Destructive action without confirmation dialog

handleDeleteFolder immediately moves all flags to root and shows a success toast without asking the user to confirm. Per the project's UI guidelines, destructive or irreversible actions must use an AlertDialog. A folder with many nested flags could be unintentionally destroyed with a single click — and there's no undo mechanism.

Consider wrapping this in a confirmation dialog similar to the DeleteDialog already used for flag deletion on the same page.

Context Used: Context from dashboard - .cursor/rules/ui-guidelines.mdc (source)

Comment on lines +334 to +341
<Button
className="size-6"
onClick={() => setIsCreating(true)}
size="icon"
variant="ghost"
>
<FolderPlusIcon className="size-3.5" weight="duotone" />
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing aria-label on icon-only button

This icon-only button has no accessible label. Per the project's UI and accessibility guidelines, icon-only buttons must include an aria-label.

Suggested change
<Button
className="size-6"
onClick={() => setIsCreating(true)}
size="icon"
variant="ghost"
>
<FolderPlusIcon className="size-3.5" weight="duotone" />
</Button>
<Button
aria-label="Create folder"
className="size-6"
onClick={() => setIsCreating(true)}
size="icon"
variant="ghost"
>
<FolderPlusIcon className="size-3.5" weight="duotone" />
</Button>

Context Used: Context from dashboard - .cursor/rules/ui-guidelines.mdc (source)

Comment on lines +12 to +14

const FOLDER_NAME_REGEX = /^[a-zA-Z0-9_\-/ ]*$/;

Copy link
Contributor

Choose a reason for hiding this comment

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

Constant declared between import groups

FOLDER_NAME_REGEX is declared between two import blocks (line 12, sandwiched between the phosphor-icons import and framer-motion import). This breaks the standard convention of grouping all imports together at the top of the file before any declarations.

Suggested change
const FOLDER_NAME_REGEX = /^[a-zA-Z0-9_\-/ ]*$/;
import { AnimatePresence, motion } from "framer-motion";

Move the constant after all imports:

import type { Flag } from "./types";

const FOLDER_NAME_REGEX = /^[a-zA-Z0-9_\-/ ]*$/;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@izadoesdev izadoesdev closed this Feb 18, 2026
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.

3 participants