Batching Brilliance: Streamlining Workflows with Multi-Select & Group Actions
Just wrapped a deep dive into enhancing our action points dashboard, delivering powerful multi-select capabilities for group workflow creation and bulk deletion. Come for the productivity boost, stay for the TypeScript Set iteration gotcha!
It’s 2 AM UTC, the kind of hour where code flows either like pure magic or stubborn molasses. Tonight, it was mostly magic, punctuated by one of those classic TypeScript "gotchas" that makes you want to both scream and high-five yourself for figuring it out. The mission: elevate our action points experience by introducing robust multi-select functionality for both creating intelligent group workflows and performing efficient bulk deletions.
Consider it a session handoff, a "letter to myself" for future context, but polished for you, fellow developer.
The Productivity Leap: Group Workflows & Bulk Deletion
Our goal was simple on paper, complex in execution: empower users to select multiple action points and either process them collectively through a new, intelligent group workflow, or simply delete them en masse. This wasn't just about adding buttons; it was about fundamentally changing how users interact with their task lists, moving from a one-by-one grind to a streamlined, batch-processing powerhouse.
Feature 1: The Intelligent Group Workflow
Imagine having a dozen related action points. Instead of tackling them individually, you can now select them all and kick off a single, overarching workflow. This isn't a simple concatenation; it's an intelligent process designed to provide structure and leverage our AI capabilities effectively.
How it Works Under the Hood:
-
The UI: We updated
src/app/(dashboard)/dashboard/action-points/page.tsxandsrc/app/(dashboard)/dashboard/projects/[id]/page.tsxto include checkboxes next to each action point. Selecting items reveals a sleek, floating action bar at the bottom of the screen. This bar houses our new "Create Group Workflow" and "Delete" buttons. Critically, we added cross-project validation – you can only create a group workflow for action points within the same project. This prevents logical inconsistencies and keeps the AI focused. -
The Brains (Server-Side):
- The core logic for structuring these group workflows lives in
src/server/services/group-prompt-builder.ts. Here, I implementedbuildGroupWorkflowSteps(). This function orchestrates anN+2step process:- Step 1: Group Analysis: An initial AI step to understand the collective context and identify overarching themes or dependencies among the selected items.
- Steps 2 to N+1: Per-Item Plans: For each of the
Nselected action points, a dedicated AI step generates a specific plan or action, often with a persona override relevant to its category (thanks toresolvePersonasForCategories()). - Step N+2: Synthesis Review: A final AI step to review the individual plans, synthesize them, and provide a comprehensive overview or next steps.
- We also added
validateGroupBudget()to ensure these multi-step workflows don't break the bank, anduniqueLabel()to help with internal tracking.
- The core logic for structuring these group workflows lives in
-
The API: A new
createGroupWorkflowmutation was added tosrc/server/trpc/routers/action-points.ts. This mutation:- Validates that all selected items belong to the same project and are not already marked as "done."
- Auto-discovers relevant context from the selected items.
- Creates the complex
N+2Workflow, intelligently assigningpersonaIdoverrides for each individual step using our existingWorkflowStep.personaIdfield. This means no new database schema changes were needed – a win for rapid deployment! - For these group workflows, we explicitly set
axiomModeto"all", ensuring comprehensive AI processing across all stages.
Feature 2: The Swift Cleanup Crew (Bulk Delete)
Sometimes you just need to clear the clutter. The same multi-select UI and floating action bar now offer a "Delete" button, making it trivial to remove multiple irrelevant action points.
Implementation Details:
- UI: Reuses the multi-select checkboxes and floating action bar from the group workflow feature. Consistency is key!
- API: A
deleteManymutation was added tosrc/server/trpc/routers/action-points.ts.- It's tenant-scoped, ensuring users can only delete items within their own tenancy.
- A safety net: a maximum of 50 items can be deleted in a single bulk operation, preventing accidental mass destruction.
The "Aha!" Moment: TypeScript, Sets, and Iteration Woes
Every dev session has its moment of head-scratching. Tonight's came courtesy of TypeScript and JavaScript Set objects.
I naturally reached for the spread syntax ([...selectedIds]) when converting a Set<string> to an array, or creating a new Set from an existing one ([...new Set(...)]). However, TypeScript threw TS2802: 'Set<string>' can only be iterated with the '--downlevelIteration' flag.
// What I tried (and failed with):
const selectedIds = new Set<string>(['id1', 'id2']);
const selectedArray = [...selectedIds]; // TS2802!
const newSet = new Set([...existingSet, 'newId']); // TS2802!
The Lesson Learned: Our codebase does not enable the --downlevelIteration compiler option. This means that while modern JavaScript environments support iterating Sets and Maps directly with spread syntax, our specific build configuration (likely targeting older environments or just not having that flag enabled for other reasons) prevents it.
The Workaround (and best practice here): Always use Array.from() for converting Sets or Maps to arrays when --downlevelIteration is not guaranteed.
// The fix:
const selectedIds = new Set<string>(['id1', 'id2']);
const selectedArray = Array.from(selectedIds); // Works!
const newSet = new Set(Array.from(existingSet).concat('newId')); // Also works!
// Or, more simply if you just want to add: existingSet.add('newId');
// If creating a *new* set from existing and new items:
const combinedIds = Array.from(existingSet).concat(['newId1', 'newId2']);
const newCombinedSet = new Set(combinedIds); // Works!
This is a critical reminder: know your project's tsconfig.json! Or, when in doubt for maximum compatibility, Array.from() is your friend for iterables.
The State of Play & What's Next
Both features are now complete, committed (b2aafe9 for group workflow, f734767 for bulk delete), and pushed to origin/main. Typecheck is clean, and our 180 unit tests are all passing.
The immediate next steps involve thorough E2E testing:
- Verify the full
N+2group workflow creation and subsequent execution. - Confirm bulk delete functionality across different scenarios.
- And, a small UX tweak: considering a "Group Workflow" indicator badge on cards linked to these multi-item workflows for better clarity.
This session was a solid win for user productivity and a good reminder about the subtle nuances of JavaScript iteration in a TypeScript context. Onwards to more streamlined development!
{
"thingsDone": [
"Implemented group workflow creation with multi-select UI and floating action bar.",
"Developed `group-prompt-builder.ts` for N+2 step workflow logic (Group Analysis -> N per-item plans -> Synthesis review) including persona overrides.",
"Added `createGroupWorkflow` tRPC mutation with validation (same-project, no-done-items, budget, context discovery).",
"Implemented bulk delete functionality with multi-select UI and floating action bar.",
"Added `deleteMany` tRPC mutation with tenant-scoping and 50-item limit.",
"Updated dashboard pages (`action-points/page.tsx`, `projects/[id]/page.tsx`) for multi-select and floating action bar.",
"Ensured typecheck cleanliness and 180/180 unit tests passing."
],
"pains": [
"Encountered `TS2802: 'Set<string>' can only be iterated with the '--downlevelIteration' flag.` when using spread syntax on Sets.",
"Had to refactor Set iteration to use `Array.from()` consistently due to project's TypeScript configuration."
],
"successes": [
"Successfully implemented complex multi-select UI for two distinct, powerful features.",
"Designed a flexible N+2 workflow structure for AI-driven group processing.",
"Reused existing DB schema (`WorkflowStep.personaId`) for per-step persona overrides, avoiding schema migrations.",
"Maintained high test coverage and type safety throughout the development process.",
"Identified and resolved a specific TypeScript configuration-related iteration issue with Sets."
],
"techStack": [
"TypeScript",
"Next.js",
"React",
"tRPC",
"Zod (for validation implicitly)",
"AI/Prompt Engineering (conceptual)",
"Jest (for unit tests)"
]
}