Conversation
WalkthroughAdds tags and appearance-first features across database migrations, schema, DB functions, React Query hooks/mutations, UI components/modals/editors, canvas appearance-driven rendering, virtualized marcher lists, E2E tests, and supporting refactors and snapshots. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Tag Editor / TagButtons
participant Mutations as Tag Mutations
participant DB as Database (drizzle)
participant Query as React Query Cache
participant Visuals as marcherWithVisuals
participant Canvas
User->>UI: create tag / add marchers to tag
UI->>Mutations: call createNewTagFromMarcherIds / createTagAppearances
Mutations->>DB: perform transaction (insert tag, marcher_tags, tag_appearances, history)
DB-->>Mutations: return created records
Mutations->>Query: invalidate tag queries and marcher-with-visuals keys
Query->>Visuals: refetch and recompute marcher visuals map
Visuals-->>Canvas: emit updated MarcherVisualMap
Canvas-->>User: re-render marchers with new appearances
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
openmarch-website | 9318bac | Dec 06 2025, 06:04 AM |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/ui/src/components/base/Button.tsx (1)
19-19: Remove trailing whitespace in red variant className.There's a trailing space in the red variant definition that should be trimmed for consistency.
- red: ["bg-red text-text-invert "], + red: ["bg-red text-text-invert"],apps/desktop/src/hooks/queries/useSectionAppearances.ts (1)
111-157: Potential race condition in query invalidations.The conditional invalidations for
byIdandbySectionqueries (lines 128-141) execute in parallel with the main invalidation chain (lines 143-151), which could cause inconsistent cache states ifmarcherWithVisualsKeysis invalidated before the specific queries complete.Consider chaining all invalidations sequentially:
onSuccess: (_, variables) => { // Invalidate specific queries const itemIds = new Set<number>(); const sections = new Set<string>(); for (const modifiedArgs of variables) { itemIds.add(modifiedArgs.id); if (modifiedArgs.section) { sections.add(modifiedArgs.section); } } - // Invalidate by ID queries - if (itemIds.size > 0) - void qc.invalidateQueries({ - queryKey: Array.from(itemIds).map((id) => - sectionAppearanceKeys.byId(id), - ), - }); - - // Invalidate by section queries - if (sections.size > 0) - void qc.invalidateQueries({ - queryKey: Array.from(sections).map((section) => - sectionAppearanceKeys.bySection(section), - ), - }); - - void qc + // Build promises for all invalidations + const invalidations: Promise<void>[] = []; + + if (itemIds.size > 0) { + invalidations.push( + qc.invalidateQueries({ + queryKey: Array.from(itemIds).map((id) => + sectionAppearanceKeys.byId(id), + ), + }) + ); + } + + if (sections.size > 0) { + invalidations.push( + qc.invalidateQueries({ + queryKey: Array.from(sections).map((section) => + sectionAppearanceKeys.bySection(section), + ), + }) + ); + } + + invalidations.push( + qc.invalidateQueries({ + queryKey: [KEY_BASE], + }) + ); + + // Wait for all invalidations before invalidating dependent queries + void Promise.all(invalidations).then(() => { + void qc.invalidateQueries({ + queryKey: marcherWithVisualsKeys.all(), + }); + }); - .invalidateQueries({ - queryKey: [KEY_BASE], - }) - .then(() => { - void qc.invalidateQueries({ - queryKey: marcherWithVisualsKeys.all(), - }); - }); },apps/desktop/src/global/classes/canvasObjects/CanvasMarcher.ts (1)
66-132: Excellent refactoring: centralized shape creation.The static
createMarkerShapemethod centralizes all shape creation logic (circle, square, triangle, X) in one place. This:
- Eliminates code duplication between constructor and
setAppearance- Makes shape creation consistent
- Simplifies future shape additions
- Properly handles the X shape's special rendering needs (using stroke color instead of fill)
🧹 Nitpick comments (30)
apps/desktop/src/components/sidebar/SidebarModal.tsx (1)
18-30: Consider removing unusedisFocusedtracking.The
isFocusedstate is tracked (lines 8-16) and included in the effect dependencies (line 30), but it's no longer used after removing the focus check from the Escape key handler (line 20).Apply this diff to remove the unused state:
export default function SidebarModal() { const { isOpen, content, setOpen } = useSidebarModalStore(); - const [isFocused, setIsFocused] = useState(false); - - const handleFocus = () => { - setIsFocused(true); - }; - - const handleBlur = () => { - setIsFocused(false); - }; useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { setOpen(false); } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [setOpen, isFocused]); + }, [setOpen]); return ( <div tabIndex={0} - onFocus={handleFocus} - onBlur={handleBlur} className={`rounded-6 border-stroke bg-modal shadow-fg-1 backdrop-blur-32 absolute top-0 left-[50px] z-40 h-full min-h-0 max-w-[36rem] overflow-hidden border p-12 outline-hidden ${ isOpen ? "animate-scale-in flex" : "hidden" }`} > {content} </div> ); }apps/desktop/src/__mocks__/generators.ts (1)
362-401: Redundant type assertion.The explicit
as DatabaseMarcherPageassertion on line 400 is unnecessary since the function's return type is already declared asDatabaseMarcherPage(line 372). TypeScript will automatically enforce this type constraint.Apply this diff to remove the redundant assertion:
return { id, marcher_id: marcher.id, page_id: page.id, x, y, path_data_id: pathDataId, path_start_position: pathStartPosition, path_end_position: pathEndPosition, notes, rotation_degrees: rotationDegrees, created_at: faker.date.recent().toISOString(), updated_at: faker.date.recent().toISOString(), - } as DatabaseMarcherPage; + }; };apps/desktop/src/components/tags/TagsModel.tsx (1)
1-5: Filename typo: "TagsModel.tsx" should likely be "TagsModal.tsx".The filename contains "Model" but the component is
TagsModalLauncher, which suggests this should beTagsModal.tsxfor consistency with the component naming.apps/desktop/src/components/ui/ColorPicker.tsx (2)
36-47: Docstring mentions non-existentsizeparameter.Line 44 documents a
sizeparameter ("Compact or default") that doesn't exist in the component's props interface.Apply this diff:
* @param defaultColor - The default color of the color picker * @param onChange - The function to call when the color changes +* @param onBlur - The function to call when the color picker loses focus * @param className - The class name of the color picker -* @param size - Compact or default * @param doNotUseForm - Whether to use the form field or not (if true, the color picker will not be inside a form field)
129-196: Significant code duplication between form and non-form paths.The popover content (lines 129-196) is nearly identical to
DefaultColorPicker(lines 222-290). SinceDefaultColorPickeralready exists, reuse it in theelsebranch to eliminate duplication.Apply this diff to the
elsebranch:else return ( <FormField label={label} tooltip={tooltip} ref={pickerRef} className={className} > - <div className="flex items-center gap-8"> - <Popover.Root> - ... (duplicate content) - </Popover.Root> - - <Button - ... (duplicate content) - </Button> - </div> + {internalComponent} </FormField> );Also applies to: 222-290
apps/desktop/src/global/classes/__test__/MarcherPage.test.ts (1)
65-65: Consider removingPartial<>type wrapper for stronger type safety.The test data is declared as
Partial<DatabaseMarcherPage>[]but then cast toDatabaseMarcherPage[]when calling the function. Since the test data appears to include all required fields, usingPartial<>with an explicit cast undermines TypeScript's compile-time type checking and could mask missing fields.Apply this diff to use the full type:
- const testMarcherPages: Partial<DatabaseMarcherPage>[] = [ + const testMarcherPages: DatabaseMarcherPage[] = [ { id: 1, marcher_id: 1, ... }, ]; const result = databaseMarcherPagesToMarcherPages( - testMarcherPages as DatabaseMarcherPage[], + testMarcherPages, );Also applies to: 111-111
apps/desktop/src/hooks/queries/usePages.ts (1)
243-249: Parameter naming inconsistency:txused forDbConnection.The
getPagesInOrderfunction is called with{ tx: db }, butdbis aDbConnection, not aDbTransaction. While the function signature acceptsDbConnection | DbTransaction, the parameter nametx(short for transaction) is misleading when passing a connection.Consider renaming the parameter in
getPagesInOrderto something likeconnor keeping it generic, or update the call site comment to clarify. This is a minor naming nit.- queryFn: async () => await getPagesInOrder({ tx: db }), + queryFn: async () => await getPagesInOrder({ tx: db }), // db is DbConnection, tx accepts bothapps/desktop/src/components/inspector/MarcherEditor.tsx (1)
485-493: Redundant Set creation for selectedMarcherIds.
selectedMarcherIdsis already computed as aSetviauseMemoat line 186-189. Creating a new Set here is redundant.<TagButtons - selectedMarcherIds={ - new Set( - selectedMarchers.map( - (marcher) => marcher.id, - ), - ) - } + selectedMarcherIds={selectedMarcherIds} />apps/desktop/e2e/utils/marchers.mts (1)
1-24: New e2e helper for creating marchers.The helper encapsulates the marcher creation flow well. However, some selectors may be fragile:
Line 10:
{ name: "Other" }- this appears to be a fallback/placeholder label. Consider using a more stable selector or adding a data-testid.Lines 19-23: The selector
.filter({ hasText: "MarchersMarcher" }).nth(4)is fragile as it depends on specific DOM structure and index positioning. Consider adding a data-testid to the target element for more reliable selection.- await page - .locator("div") - .filter({ hasText: "MarchersMarcher" }) - .nth(4) - .press("Escape"); + // Consider using a more stable selector like: + // await page.getByTestId("marcher-list-container").press("Escape"); + await page + .locator("div") + .filter({ hasText: "MarchersMarcher" }) + .nth(4) + .press("Escape");apps/desktop/src/components/marcher/MarcherList.tsx (1)
318-327: Type inconsistency: RefObject vs MutableRefObject for changesRef.
VirtualizedMarcherRowsPropsusesRefObject<{ [key: number | string]: unknown }>(line 326), butMarcherRowPropsusesMutableRefObject<{ [key: number | string]: unknown }>(line 403). SincechangesRefis being mutated (values are assigned inhandleChange),MutableRefObjectis the correct type.interface VirtualizedMarcherRowsProps { localMarchers: Marcher[]; isEditing: boolean; handleChange: (value: string, attribute: string, marcherId: number) => void; handleDeleteMarcher: (marcherId: number) => void; sectionOptions: SectionOption[]; sectionLabelMap: Record<string, string>; selectPlaceholder: string; - changesRef: RefObject<{ [key: number | string]: unknown }>; + changesRef: MutableRefObject<{ [key: number | string]: unknown }>; }apps/desktop/src/global/classes/canvasObjects/OpenMarchCanvas.ts (1)
23-23: Type workaround ingetRoundedCoordinateis safe but could be tightenedSwitching
fakeMarcherPagetoPartial<MarcherPage>and casting toMarcherPageonly at thegetRoundCoordinatescall avoids compile-time breakage from newly required fields and doesn’t change runtime behavior, since onlyxandyare used.You might consider defining a narrower helper type (e.g.
type CoordMarcherPage = Pick<MarcherPage, "id" | "marcher_id" | "page_id" | "x" | "y">;) and updatinggetRoundCoordinatesto accept that (or just{ x, y }) long-term, to drop theas MarcherPagecast.Also applies to: 1365-1382
apps/desktop/src/components/exporting/MarcherCoordinateSheet.tsx (1)
58-60: Narrowed marcher-page type and row rendering look correctRestricting
marcherPagesto aPickof only the fields needed for coordinate output is a nice decoupling from the fullMarcherPage/appearance surface, and state initialization follows that type correctly. The new<tbody>implementation cleanly joins each marcher page to itsPage, usesmeasureRangeString(page)whenincludeMeasuresStateis true, and renders X/Y viaReadableCoordswith the configured rounding and terse/verbose modes. No functional issues spotted here.If you end up reusing this subset elsewhere, consider extracting a shared alias (e.g.
type MarcherPageCoords = Pick<MarcherPage, "id" | "marcher_id" | "page_id" | "x" | "y">;) to avoid repeating the Pick.Also applies to: 244-247, 311-312, 526-596
apps/desktop/src/components/marcher/appearance/SectionAppearanceList.tsx (1)
62-71: AlignavailableSectionslogic and button disabled state with intentRight now
availableSectionsis all section names (lines 64–67) while the comment says “sections without appearances”, and the “Add section style” trigger is only disabled whenavailableSections.length === 0(Line 97), which will never happen as long as SECTIONS is non‑empty. The real constraint is whether there is any section not present intakenSections.Consider:
- Deriving
availableSectionsfrom SECTIONS but filtering outtakenSections, and- Using that filtered list both for the “All” tab and for the
disabledcondition on the trigger.For example:
- const defaultShapeType = "circle"; - - // Get available sections (sections without appearances) - const availableSections = Object.values(SECTIONS).map( - (section) => section.name, - ); + const defaultShapeType = "circle"; @@ - /** A set of all sections that have an appearance */ - const takenSections = useMemo(() => { - return new Set( - sectionAppearancesSorted.map((appearance) => appearance.section), - ); - }, [sectionAppearancesSorted]); + /** A set of all sections that have an appearance */ + const takenSections = useMemo( + () => + new Set( + sectionAppearancesSorted.map( + (appearance) => appearance.section, + ), + ), + [sectionAppearancesSorted], + ); + + // Sections without existing appearances + const availableSections = useMemo( + () => + Object.values(SECTIONS) + .map((section) => section.name) + .filter((name) => !takenSections.has(name)), + [takenSections], + );The rest of the code (trigger
disabled={availableSections.length === 0}andavailableSections.map(...)in the “All” tab) can remain as is and will now reflect the actual availability.Also applies to: 95-103, 156-179
apps/desktop/src/db-functions/__test__/tag.test.ts (1)
287-425: Consider avoidingfc.sampleinside properties for clearer shrinking and reproducibilityThe property-based tests under
"multiple appearances of same tag"(around Lines 289–425) create additional randomness viafc.sample(...)inside thefc.propertybody. This works, but:
- It makes failing cases slightly harder to reproduce from a seed.
- It bypasses fast-check’s normal shrinking on those inner generated values.
If you ever need to debug a failing case here, it will be simpler to express these as proper arbitraries instead, for example by:
- Defining an arbitrary for the per-tag appearance start pages (e.g.,
fc.uniqueArray(fc.integer(...))), and- Passing that arbitrary as a normal argument to
fc.propertyinstead of sampling inside.This is a style/perf tweak only; the current tests are logically correct.
apps/desktop/src/components/marcher/appearance/AppearanceModal.tsx (1)
9-36: Clarify naming and hard localStorage dependency inAppearanceModalTwo minor points:
The default export is still called
MetronomeModal(Lines 9–24) even though this file isAppearanceModal.tsxand the contents are all appearance-related. Renaming the component (and call sites) to something likeAppearanceModalorAppearanceModalLauncherwould make this easier to discover and reason about.
AppearanceModalContentsreads fromlocalStorageat render time (Lines 33–36). That’s fine in the Electron renderer, but it will throw if this component is ever imported in a non-browser test environment. A small guard keeps it robust:- const localStorageMode = localStorage.getItem("marcher-appearance-mode"); + const localStorageMode = + typeof window !== "undefined" + ? window.localStorage.getItem("marcher-appearance-mode") + : null;Both are non-breaking cleanups that improve maintainability and testability.
apps/desktop/src/components/canvas/Canvas.tsx (1)
10-11: Avoid duplicating marcher fetching now thatmarcherWithVisualsQueryOptionsexistsThe new
marcherWithVisualsQueryOptionsintegration (Lines 10–11, 56–58) looks good, and the additional guards onmarcherVisuals == nullbefore rendering are sensible.One small inefficiency: you still have a separate
useQuery(allMarchersQueryOptions())(Line 53), butmarcherWithVisualsQueryOptionsitself callsqueryClient.fetchQuery(allMarchersQueryOptions())internally when building the visual groups. That means:
- Marcher data is fetched twice at the query layer (once via
useQuery, once viafetchQuery), and- In several effects (Lines ~220–289, 305–374, 438–447) you only really need marcher IDs, which can likely be derived from
marcherVisualsinstead.If you want to tighten this up, you could:
- Drop the top-level
const { data: marchers } = useQuery(allMarchersQueryOptions());, and- Derive IDs from
marcherVisuals(e.g.,Object.keys(marcherVisuals).map(Number)or via a helper on the visual group structure) where you currently rely onmarchers.This would centralize marcher data loading in the visuals query and avoid redundant fetching, while keeping the current rendering guards intact.
Also applies to: 56-58, 220-289, 305-374, 438-447
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx (1)
34-36: Map storestag.nameinstead of the derived display name.
tagNamesByTagIdstorestag.namewhich can benull, but consumers callgetTagNamewith this value anyway. This works but is slightly misleading since the map doesn't contain "tag names" but raw name values.Consider storing the derived name directly for clarity:
const tagNamesByTagId = useMemo(() => { - return new Map(allTags?.map((tag) => [tag.id, tag.name]) ?? []); + return new Map( + allTags?.map((tag) => [ + tag.id, + getTagName({ tag_id: tag.id, name: tag.name }), + ]) ?? [], + ); }, [allTags]);apps/desktop/src/components/inspector/marcher/TagEditor.tsx (3)
98-98: Non-null assertion on context could cause runtime error.
useSelectedPage()!assumes the context is always available. If this component is rendered outside the provider, it will throw.Consider adding a guard or using optional chaining:
- const { selectedPage } = useSelectedPage()!; + const selectedPageContext = useSelectedPage(); + const selectedPage = selectedPageContext?.selectedPage;
193-196: Hardcoded UI strings should use i18n (Tolgee).Several user-facing strings are hardcoded instead of using the
<T>component ort()function: "Rename tag", "Edit appearance", "Delete tag".Replace hardcoded strings with translation keys:
- <label className="text-text-subtitle text-sm"> - Rename tag - </label> + <label className="text-text-subtitle text-sm"> + <T keyName="tags.rename" /> + </label>Also applies to: 215-216, 221-222, 228-229
308-313: Another hardcoded string that should be internationalized."Define a name for the new tag (optional)" should use a translation key.
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx (1)
56-68: Unnecessaryasync/awaiton non-async callback.
handleUpdateAppearancereturnsvoidper the type signature, so theasynckeyword andawaitare unnecessary and may cause confusion.- async function handleChange(changes: Partial<AppearanceComponentOptional>) { - await handleUpdateAppearance({ + function handleChange(changes: Partial<AppearanceComponentOptional>) { + handleUpdateAppearance({ visible: changes.visible !== undefined ? changes.visible : appearance.visible, label_visible: changes.label_visible !== undefined ? changes.label_visible : appearance.label_visible, ...changes, }); }apps/desktop/src/components/toolbar/tabs/SelectTab.tsx (2)
19-19: Import from index file instead of specific module.Based on learnings, db-functions should be imported from the index file.
-import { getTagName } from "@/db-functions/tag"; +import { getTagName } from "@/db-functions";
45-45: Non-null assertion on context hook.Similar to TagEditor.tsx,
useSelectedMarchers()!assumes the context is always available.Consider adding a guard:
- const { selectedMarchers, setSelectedMarchers } = useSelectedMarchers()!; + const context = useSelectedMarchers(); + if (!context) { + throw new Error("useHandleSelect must be used within SelectedMarchersProvider"); + } + const { selectedMarchers, setSelectedMarchers } = context;.cursor/rules/basic-database-interaction.mdc (1)
139-162: Consider returning early without warning for expected empty input.The
console.warnon line 148 may be noisy if empty arrays are a valid use case. Consider whether this should be a silent early return or if the warning is intentional for debugging.export async function create{tableName}s({ newItems, db, }: { newItems: New{tableName}Args[]; db: DbConnection; }): Promise<Database{tableName}[]> { if (newItems.length === 0) { - console.warn("No new items to create"); return []; }apps/desktop/src/global/classes/MarcherVisualGroup.ts (1)
125-146: Complex visibility logic may benefit from clarification.The
isHiddenmethod's logic is subtle: when there are multiple appearances and the first (marcherPage) is visible, it checks the second appearance's visibility. The inline comment explains this, but the logic could be clearer.Consider extracting the visibility resolution to a helper or adding a more explicit comment about why marcherPage visibility is skipped when true:
static isHidden( appearancesInput: | AppearanceComponentOptional[] | AppearanceComponentOptional, ) { const appearances = Array.isArray(appearancesInput) ? appearancesInput : [appearancesInput]; - let visible: boolean; - if (appearances.length > 0) - if (appearances.length > 1 && appearances[0].visible) { - // If there is more than one appearance, and the marcherPage appearance is true, use the next one - // This is because the marcherPage will almost always be visible - visible = appearances[1].visible; - } else { - visible = appearances[0].visible; - } - else visible = true; + if (appearances.length === 0) { + return false; // Default to visible when no appearances + } + + // Priority: If marcherPage (first) is visible and there's a tag/section appearance, + // defer to the tag/section appearance since marcherPage.visible is usually true. + const visible = + appearances.length > 1 && appearances[0].visible + ? appearances[1].visible + : appearances[0].visible; return !visible; }apps/desktop/src/hooks/queries/useMarchersWithVisuals.ts (1)
147-147: Document rationale for eslint-disable.The
@tanstack/query/exhaustive-depsdisable suppresses warnings aboutqueryClientnot being in the key. Consider adding a brief comment explaining why this is intentional (e.g.,queryClientis stable singleton).- // eslint-disable-next-line @tanstack/query/exhaustive-deps + // eslint-disable-next-line @tanstack/query/exhaustive-deps -- queryClient is a stable singleton from App.tsx queryKey: marcherWithVisualsKeys.byPageId(pageId!),apps/desktop/electron/database/migrations/schema.ts (1)
27-39: Good pattern: reusable appearance columns.Exporting
appearance_columnsas a shared object and spreading it into multiple tables (marcher_pages, section_appearances, tag_appearances) is an excellent pattern that:
- Ensures consistency across appearance-related fields
- Reduces duplication
- Makes schema evolution easier
Consider adding JSDoc comments to document each field in
appearance_columns, especiallyshape_type,equipment_name, andequipment_state, to clarify their purpose and expected values.apps/desktop/src/entity-components/appearance.ts (1)
17-32: Consider edge case handling inparseRgbaColor.The regex pattern handles both
rgb()andrgba()formats correctly, with a sensible default fallback{ r: 0, g: 0, b: 0, a: 1 }.However, the function silently returns a default black color for invalid input. Consider whether this should:
- Log a warning for invalid color strings
- Return null instead of a default color
- Throw an error for invalid input
Example enhancement:
function parseRgbaColor(colorStr: string): RgbaColor { const match = colorStr.match( /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d*\.?\d+))?\)/, ); if (match) { return { r: parseInt(match[1], 10), g: parseInt(match[2], 10), b: parseInt(match[3], 10), a: match[4] ? parseFloat(match[4]) : 1, }; } + console.warn(`Invalid color string: ${colorStr}, using default black`); return { r: 0, g: 0, b: 0, a: 1 }; }apps/desktop/src/global/classes/canvasObjects/CanvasMarcher.ts (1)
36-37: Verify ifdotObjectneeds to be public.The
dotObjectfield has been changed fromprivatetopublic(no explicit modifier). This makes it accessible outside the class, which could lead to unintended mutations.While this change appears necessary for the
setAppearancemethod to dynamically replace the marker shape, consider:
- Whether external code needs to access this field
- If a getter method would be more appropriate
- Adding documentation about mutability expectations
apps/desktop/src/db-functions/tag.ts (1)
372-385: Inconsistent naming forinTransactionfunctions.The tag appearance
inTransactionfunctions (createTagAppearancesInTransaction,updateTagAppearancesInTransaction,deleteTagAppearancesInTransaction) don't follow the underscore-prefix convention used by marcher tag functions (_createMarcherTagsInTransaction,_updateMarcherTagsInTransaction).Per coding guidelines, private database operation functions should be prefixed with underscore.
Also applies to: 410-436, 463-476
apps/desktop/src/components/exporting/utils/__test__/svg-generator.test.ts
Show resolved
Hide resolved
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx
Outdated
Show resolved
Hide resolved
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
Outdated
Show resolved
Hide resolved
|
@dukcc check out the new tagging UI in the marcher editor, the marcher appearance sidebar, and the select topbar |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
apps/desktop/src/components/ui/ColorPicker.tsx (1)
61-64: BothonChangeandonBlurfire on close - potential double callback.This was previously flagged but remains unaddressed. When
handleCloseis called, both callbacks fire sequentially, which could cause duplicate state updates if a consumer provides the same handler to both props.apps/desktop/src/db-functions/tag.ts (1)
101-137: Missing public wrapper functions forupdateTagsanddeleteTags.Based on coding guidelines, all database mutations should use
transactionWithHistoryfor automatic undo/redo support. WhilecreateTags(lines 69-84) correctly wraps_createTagsInTransactionwithtransactionWithHistory, theupdateTagsInTransactionanddeleteTagsInTransactionfunctions are exported directly without public wrappers.This forces callers to either use the transaction functions directly (bypassing history tracking) or manually wrap them. For consistency and to ensure proper undo/redo support, add public wrapper functions similar to
createTags.Based on learnings, database mutations must use
transactionWithHistoryfor automatic undo/redo support.
🧹 Nitpick comments (4)
apps/desktop/src/components/ui/ColorPicker.tsx (1)
27-27: Consider renamingdoNotUseFormto avoid double-negative.The flag name
doNotUseFormrequires mental negation to understand behavior. A positive alternative likeuseStaticField,standalone, ordisableFormWrapperwould improve readability.apps/desktop/src/components/inspector/marcher/TagEditor.tsx (1)
34-38: Consider showing a loading state.The component returns
nullwhile tags are loading. For better UX, consider showing a loading indicator or skeleton, especially if the load takes noticeable time.apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx (1)
32-37: Minor cleanup: simplifyhandleChangeand tighten React type importsIf
handleUpdateAppearanceis intended to be synchronous (as currently typed), you can simplify a bit:
- Drop
async/awaitfromhandleChangeand call it directly from handlers, removing the need forvoidwrappers.- Import
ReactNodefrom"react"instead of using theReactnamespace, which avoids relying on a global React type and is more idiomatic.Example diff:
+import type { ReactNode } from "react"; import { AppearanceComponent, AppearanceComponentOptional, } from "@/entity-components/appearance"; @@ -const shapeIcons: Record<ShapeType, React.ReactNode> = { +const shapeIcons: Record<ShapeType, ReactNode> = { @@ -export function AppearanceEditor({ +export function AppearanceEditor({ @@ - async function handleChange(changes: Partial<AppearanceComponentOptional>) { - await handleUpdateAppearance({ + function handleChange(changes: Partial<AppearanceComponentOptional>) { + handleUpdateAppearance({ visible: changes.visible !== undefined ? changes.visible : appearance.visible, @@ function onColorChange( attribute: "fill_color" | "outline_color", color: RgbaColor | null, ) { - void handleChange({ [attribute]: color }); + handleChange({ [attribute]: color }); } @@ function onShapeChange(shape: string) { - // Convert the special "__none__" value to null - void handleChange({ shape_type: shape === "__none__" ? null : shape }); + // Convert the special "__none__" value to null + handleChange({ shape_type: shape === "__none__" ? null : shape }); } const handleVisibilityChange = () => { - void handleChange({ visible: !appearance.visible }); + handleChange({ visible: !appearance.visible }); }; const handleLabelVisibilityChange = () => { - void handleChange({ - label_visible: !appearance.label_visible, - }); + handleChange({ label_visible: !appearance.label_visible }); };If you do expect
handleUpdateAppearanceto become async, instead update its type to() => Promise<void>so the currentasync/await+voidpattern stays consistent.Also applies to: 56-88
apps/desktop/src/db-functions/__test__/page.test.ts (1)
2878-3161: TightengetPagesInOrdertests for robustness and clarityThe tests broadly look good and exercise ordering, mapping, and edge cases. A few small refinements would make them more future‑proof and self‑documenting:
Avoid relying on
beat.id === beat.positionwhere not necessary
Several expectations assume specific positions from hard‑codedstart_beatIDs (e.g., expecting positions[0, 2, 5, 8]or[0, 1, 3, 7, 10, 12, 15]). IfcreateBeatsever changes howpositionis assigned, these tests could fail even whengetPagesInOrderis still correct.
Consider:
- Asserting only that
beatObject.positionvalues are strictly ascending (you already do this in the “many pages” test), and- Verifying the first page specifically (
FIRST_PAGE_ID/FIRST_BEAT_ID), while deriving expectations for other positions by queryingschema.beatsor by comparing to sortedresult.map(r => r.beatObject.position)instead of hard‑coding specific numeric positions.
This keeps the tests focused on ordering semantics rather than internal assumptions about ID/position coupling.Prefer typed results over
any
In a few places you uselet result: any;(e.g., “ordered by beat position ascending”, “join pages with their start beats”, and the last edge‑case test). SinceDatabasePageWithBeatis imported and some tests already use it, you could strengthen typing without much friction, for example:
let result: any;
let result: DatabasePageWithBeat[] = []; await transaction(db, async (tx) => { result = await getPagesInOrder({ tx }); });This keeps the pattern compatible with the existing `transaction` helper while giving you compile‑time checks on the result shape.
- Align test description with setup for “identical beat positions”
The test"should handle pages with identical beat positions (should not happen but test robustness)"currently usescreateBeatswith monotonically increasing positions and creates only a single non‑first page, so it does not actually exercise identical beat positions.
Either:
- Adjust the description to something like “should handle additional pages with valid beat positions”, or
- If you truly want to cover identical
beat.positionvalues, insert beats manually viatx.insert(schema.beats)with the samepositionand create pages pointing at them, then assert thatgetPagesInOrderstill returns without error and preserves a deterministic order.Overall behavior is correct; these are maintainability and clarity tweaks you can adopt if you think they’re worthwhile.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/desktop/src/components/inspector/marcher/TagEditor.tsx(1 hunks)apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx(1 hunks)apps/desktop/src/components/ui/ColorPicker.tsx(3 hunks)apps/desktop/src/db-functions/__test__/page.test.ts(2 hunks)apps/desktop/src/db-functions/sectionAppearance.ts(2 hunks)apps/desktop/src/db-functions/tag.ts(1 hunks)apps/desktop/src/hooks/queries/useMarcherPages.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/hooks/queries/useMarcherPages.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Co-locate UI utility helper functions with their components
Always use absolute paths (with@/alias forsrc/directory) for file operations, never relative paths
Batch UI refresh operations where possible for performance optimizationWhen you need to use icons, use @phosphor-icons/react
Files:
apps/desktop/src/components/inspector/marcher/TagEditor.tsxapps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/components/ui/ColorPicker.tsxapps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/components/marcher/appearance/AppearanceEditor.tsxapps/desktop/src/db-functions/tag.ts
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: UseuseNextUndoGroupparameter to control transaction grouping in database operations
Use GroupFunction for multi-operation transactions with rollback capability
Locate database functions inelectron/database/tables/(legacy IPC-based) OR consolidate insrc/global/classes/(new shared SQL bridge pattern)
Place business logic in classes located insrc/global/classes/
GroupFunction returns{responses: [], success: boolean}format, direct DB calls returnDatabaseResponse<T>format
Prefer single transactions over multiple database calls for performance
Database entity types should use Drizzle inferred types:typeof table.$inferSelectandtypeof table.$inferInsert, never manually defined interfaces that duplicate schema information
Internal database functions should use*Db()suffix and return raw data types, public wrapper functions should have the same name and applyDatabaseResponsewrapper
Omit auto-generated fields (id, timestamps) fromNewEntityArgstype definitions using Omit utility type
**/*.ts: Always await when usingtransactionWithHistory()to ensure the database action finishes in time for query invalidation
Create React Query mutation hooks that wrap database operation functions and handle error/success callbacks with Tolgee internationalization
UseconToastError()with Tolgee keys for user-friendly error messages in mutation error handlers
Invalidate all relevant query keys after successful mutations, always invalidating beats first and waiting for completion before invalidating dependent queries like measures
Use query key factories from@/hooks/querieswhen invalidating queries to ensure consistency and maintainability
Files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
apps/desktop/src/db-functions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/basic-database-interaction.mdc)
apps/desktop/src/db-functions/*.ts: Create a database function file inapps/desktop/src/db-functions/[tableName].tswhen adding new database table interactions
Database function files must include proper TypeScript types withDatabase[TableName],New[TableName]Args, andModified[TableName]Argsinterfaces
Database function files must include data transformation functions (e.g.,realDatabase[TableName]ToDatabase[TableName]) to convert between database types and application types
All database mutations (create, update, delete) must usetransactionWithHistoryfor automatic undo/redo support
Database mutations should implement separate 'inTransaction' functions (e.g.,create[TableName]sInTransaction) that accept aDbTransactionparameter
Database function files must handle database-specific type transformations (e.g., boolean as 0|1 in SQLite) with explicit conversion functions
Database function files must implement comprehensive query functions (getAll, getById, etc.) before implementing mutations
Files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/tag.ts
apps/desktop/src/{db-functions,hooks/queries}/*.ts
📄 CodeRabbit inference engine (.cursor/rules/basic-database-interaction.mdc)
Follow naming conventions: Database files as
[tableName].ts(camelCase), hook files asuse[TableName].ts(PascalCase), functions asget[TableNames],create[TableNames],update[TableNames],delete[TableNames]
Files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/tag.ts
**/*[A-Z]*.ts
📄 CodeRabbit inference engine (.cursor/rules/component-database-interaction.mdc)
**/*[A-Z]*.ts: Always usetransactionWithHistory()for operations that modify multiple database entities, need undo/redo support, or should be atomic
Keep database operation functions private and prefixed with underscore (_) in FeatureName.ts files, though they can still be exported for testing
Files:
apps/desktop/src/db-functions/sectionAppearance.ts
**/*.test.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.ts: Use.test.tssuffix for test files and place mocks in__mocks__/directories
Test internal*Dbfunctions expecting raw data arrays, test public wrapper functions expectingDatabaseResponseformat
Create foreign key references before testing dependent entities in database tests
UsesetupTestSqlProxy()in beforeEach for database test setupTest business logic functions independently of React components using real database operations (no mocks), and verify transaction rollback scenarios and query invalidation behavior
Files:
apps/desktop/src/db-functions/__test__/page.test.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/command-syntax.mdc)
When running vitest, use 'npx vitest run {test_here}'
Files:
apps/desktop/src/db-functions/__test__/page.test.ts
🧠 Learnings (28)
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Database function files must include data transformation functions (e.g., `realDatabase[TableName]ToDatabase[TableName]`) to convert between database types and application types
Applied to files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/index.ts : Update `apps/desktop/src/db-functions/index.ts` to export all new database function files
Applied to files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Database function files must include proper TypeScript types with `Database[TableName]`, `New[TableName]Args`, and `Modified[TableName]Args` interfaces
Applied to files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Database function files must handle database-specific type transformations (e.g., boolean as 0|1 in SQLite) with explicit conversion functions
Applied to files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : All database mutations (create, update, delete) must use `transactionWithHistory` for automatic undo/redo support
Applied to files:
apps/desktop/src/db-functions/sectionAppearance.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.test.ts : Test internal `*Db` functions expecting raw data arrays, test public wrapper functions expecting `DatabaseResponse` format
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Database function files must implement comprehensive query functions (getAll, getById, etc.) before implementing mutations
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:04:04.374Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/component-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:04:04.374Z
Learning: Applies to **/*.test.ts : Test business logic functions independently of React components using real database operations (no mocks), and verify transaction rollback scenarios and query invalidation behavior
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Database mutations should implement separate 'inTransaction' functions (e.g., `create[TableName]sInTransaction`) that accept a `DbTransaction` parameter
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.test.ts : Create foreign key references before testing dependent entities in database tests
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/{db-functions,hooks/queries}/*.ts : Follow naming conventions: Database files as `[tableName].ts` (camelCase), hook files as `use[TableName].ts` (PascalCase), functions as `get[TableNames]`, `create[TableNames]`, `update[TableNames]`, `delete[TableNames]`
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.tsapps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/**/*.test.ts?(x) : Test early return conditions (incomplete data) in multi-query hook combine functions
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/**/*.test.ts?(x) : Test data transformation logic in multi-query hooks with various data combinations
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/**/*.test.ts?(x) : Use real query options, not mocks, for integration testing of multi-query hooks
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:29.206Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/unit-testing.mdc:0-0
Timestamp: 2025-11-27T02:04:29.206Z
Learning: Applies to **/{test,__tests__,tests}/**/*.{test,spec}.{ts,tsx,js,jsx} : Never use mocks for packages or database operations; all functionality should be testable with real implementations, except for mocking React context providers when rendering components that rely on hooks
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:04.374Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/component-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:04:04.374Z
Learning: Applies to **/*.ts : Always await when using `transactionWithHistory()` to ensure the database action finishes in time for query invalidation
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/**/*.test.ts?(x) : Test multi-query hooks by mocking query options to return loading and error states
Applied to files:
apps/desktop/src/db-functions/__test__/page.test.ts
📚 Learning: 2025-11-27T02:04:23.301Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T02:04:23.301Z
Learning: Applies to packages/**/*.{ts,tsx} : Prefer to use components in './packages/ui/src/components/base' folder for common items like buttons and inputs
Applied to files:
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.{ts,tsx} : Always use absolute paths (with `@/` alias for `src/` directory) for file operations, never relative paths
Applied to files:
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx
📚 Learning: 2025-11-27T02:04:23.301Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T02:04:23.301Z
Learning: Applies to packages/ui/src/components/**/*.{ts,tsx} : All styles are stored in tailwind.css; match existing style patterns and color usage when making frontend components
Applied to files:
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.{ts,tsx} : Co-locate UI utility helper functions with their components
Applied to files:
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/*.ts : Create a database function file in `apps/desktop/src/db-functions/[tableName].ts` when adding new database table interactions
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.ts : Locate database functions in `electron/database/tables/` (legacy IPC-based) OR consolidate in `src/global/classes/` (new shared SQL bridge pattern)
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.ts : Database entity types should use Drizzle inferred types: `typeof table.$inferSelect` and `typeof table.$inferInsert`, never manually defined interfaces that duplicate schema information
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:04:04.374Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/component-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:04:04.374Z
Learning: Applies to **/*[A-Z]*.ts : Keep database operation functions private and prefixed with underscore (_) in FeatureName.ts files, though they can still be exported for testing
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:04:04.374Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/component-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:04:04.374Z
Learning: Applies to **/*[A-Z]*.ts : Always use `transactionWithHistory()` for operations that modify multiple database entities, need undo/redo support, or should be atomic
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Applies to **/*.ts : Internal database functions should use `*Db()` suffix and return raw data types, public wrapper functions should have the same name and apply `DatabaseResponse` wrapper
Applied to files:
apps/desktop/src/db-functions/tag.ts
📚 Learning: 2025-11-27T02:04:04.374Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/component-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:04:04.374Z
Learning: Separate business logic into `.ts` files, React integration into custom hooks, and database operations using transaction functions from `@/db-functions`
Applied to files:
apps/desktop/src/db-functions/tag.ts
🧬 Code graph analysis (4)
apps/desktop/src/components/inspector/marcher/TagEditor.tsx (6)
apps/desktop/src/hooks/queries/tags/queries.ts (2)
allTagsQueryOptions(203-211)allMarcherTagsQueryOptions(250-258)apps/desktop/src/db-functions/tag.ts (6)
DatabaseTag(23-23)getTagName(25-28)createNewTagFromMarcherIds(559-595)createMarcherTags(600-623)NewMarcherTagArgs(483-486)deleteMarcherTags(689-709)apps/desktop/src/stores/SidebarModalStore.tsx (1)
useSidebarModalStore(17-43)apps/desktop/src/context/SelectedPageContext.tsx (1)
useSelectedPage(81-83)apps/desktop/src/hooks/queries/tags/mutations.ts (5)
updateTagsMutationOptions(47-66)deleteTagsMutationOptions(71-84)createNewTagFromMarcherIdsMutationOptions(142-159)createMarcherTagsMutationOptions(164-175)deleteMarcherTagsMutationOptions(196-207)apps/desktop/src/components/marcher/appearance/AppearanceModal.tsx (1)
AppearanceModalContents(26-80)
apps/desktop/src/db-functions/sectionAppearance.ts (1)
apps/desktop/src/entity-components/appearance.ts (5)
AppearanceComponent(35-43)appearanceModelRawToParsed(65-79)AppearanceComponentOptional(46-50)AppearanceComponentRawOptional(53-57)appearanceModelParsedToRawOptional(103-120)
apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx (3)
apps/desktop/src/entity-components/appearance.ts (2)
AppearanceComponent(35-43)AppearanceComponentOptional(46-50)packages/core/src/field/FieldTheme.ts (1)
RgbaColor(3-3)apps/desktop/src/components/ui/ColorPickerMini.tsx (1)
ColorPickerMini(46-174)
apps/desktop/src/db-functions/tag.ts (3)
apps/desktop/src/db-functions/types.ts (1)
DbTransaction(8-13)apps/desktop/src/db-functions/history.ts (1)
transactionWithHistory(42-138)apps/desktop/src/entity-components/appearance.ts (6)
AppearanceComponent(35-43)appearanceModelRawToParsed(65-79)AppearanceComponentRaw(12-14)appearanceModelParsedToRawOptional(103-120)AppearanceComponentOptional(46-50)AppearanceComponentRawOptional(53-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: pr-checks (build)
- GitHub Check: build-and-release (windows-latest)
- GitHub Check: build-and-release (macos-15-intel)
- GitHub Check: build-and-release (ubuntu-24.04-arm)
- GitHub Check: build-and-release (ubuntu-latest)
- GitHub Check: build-and-release (macos-latest)
- GitHub Check: build
- GitHub Check: desktop-e2e
- GitHub Check: test
🔇 Additional comments (14)
apps/desktop/src/components/ui/ColorPicker.tsx (1)
99-110: LGTM! DefaultColorPicker correctly invoked as JSX.The previous concern about calling React components as functions has been properly addressed. The component is now rendered as a JSX element with props correctly passed as attributes.
apps/desktop/src/db-functions/tag.ts (3)
204-215: Good fix: User-provided visibility values are now preserved.The past issue where
modifiedTagAppearanceArgsToRealModifiedTagAppearanceArgsforcedvisible: trueandlabel_visible: truehas been correctly addressed. The converter now forwards the user-supplied values throughappearanceModelParsedToRawOptionalwithout overriding them.
559-595: LGTM: Proper transaction grouping for tag creation with marchers.The
createNewTagFromMarcherIdsfunction correctly usestransactionWithHistoryto atomically create both a tag and its associated marcher-tag relationships in a single transaction. This ensures data consistency and proper undo/redo support.
176-188: No action needed: Type design prevents visibility overrides on creation.The
NewTagAppearanceArgstype explicitly omitsvisibleandlabel_visiblefields (viaPartial<AppearanceComponent>, which itself omits these fields from the raw database types). New tag appearances always default to visible; callers cannot passvisible: falseat creation time due to TypeScript type constraints. To hide an appearance after creation, use the separateModifiedTagAppearanceArgstype andupdateTagAppearancesfunction, which properly accepts visibility changes.Likely an incorrect or invalid review comment.
apps/desktop/src/components/inspector/marcher/TagEditor.tsx (3)
239-346: LGTM: New tag creation flow is well-implemented.The component properly handles tag creation with keyboard shortcuts, focus management, and mutation loading states. The use of
createNewTagFromMarcherIdsensures atomic creation of both the tag and its marcher associations.
361-373: LGTM: Correctly creates marcher-tag associations.The callback properly maps each selected marcher ID to a
NewMarcherTagArgsobject with the target tag ID, ensuring all selected marchers are associated with the chosen tag in a single operation.
449-467: LGTM: Critical bug fixed - now correctly filters by selected marchers.The past issue where the filter only checked
tag_id(deleting the tag from ALL marchers) has been correctly addressed. Lines 455-461 now filter by bothtag_idAND whether themarcher_idis inselectedMarcherIds, ensuring only associations for selected marchers are removed.Minor refinement: Line 462 could check
marcherTagIds.length > 0for clarity, though the current check works.apps/desktop/src/db-functions/sectionAppearance.ts (5)
8-23: LGTM: Clean refactoring to shared appearance types.The type definitions now properly extend
AppearanceComponentand leverage shared appearance model types from@/entity-components/appearance.ts. This eliminates duplication of color/shape field definitions and ensures consistency across all appearance entities.
30-37: LGTM: Transformation properly delegates to shared converter.The refactoring to use
appearanceModelRawToParsedremoves manual color/shape/boolean parsing and ensures consistency with other appearance entities.
70-80: LGTM: Past issue resolved - section field now included.The converter now properly includes
section: args.section(line 75), fixing the previously reported bug where section updates were silently ignored. The refactoring to useappearanceModelParsedToRawOptionalmaintains clean separation of concerns.
139-139: LGTM: Appropriate log level.Using
console.debugfor this informational message reduces log noise compared toconsole.log.
47-58: This review comment does not identify an issue.The forced visibility defaults (
visible: true,label_visible: true) in the converter are safe and intentional. The UI component (SectionAppearanceList.tsx) already explicitly sets both fields totruebefore callingcreateSectionAppearances, and no callers in the codebase attempt to passvisible: falseor omit these fields. The converter enforces these defaults as a defensive guarantee, which is a standard pattern. Additionally, the database schema itself defaults both fields to1(true), providing a third layer of safety. No workflows would be broken by this behavior.apps/desktop/src/components/marcher/appearance/AppearanceEditor.tsx (1)
39-53: AppearanceEditor structure and UI wiring look solidProps, appearance merging, visibility/label toggles, color pickers, and shape select are all wired coherently, and the component follows the absolute import and phosphor-icon guidelines. ColorPickerMini usage (update on blur with nullable
RgbaColor) matches the shared appearance types, and the Select values (shapeOptions+"__none__") line up cleanly withshape_type. As per coding guidelines and retrieved learnings, this is consistent with the existing UI patterns.Also applies to: 90-172
apps/desktop/src/db-functions/__test__/page.test.ts (1)
16-17: New imports are consistent and used correctly
getPagesInOrderandDatabasePageWithBeatare imported and exercised below; the additions fit the existing test file structure without issues.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
apps/desktop/src/components/toolbar/tabs/SelectTab.tsx (3)
223-270: DrillPrefixSelector looks consistent and accessibleThe selector correctly derives unique drill prefixes, wires them through to selection, and now uses a specific
aria-label="Select drill prefixes", which addresses the earlier accessibility concern for this control. No further changes needed.
272-312: FamilySelector grouping and selection logic are clearGrouping by
SectionFamily, filtering sections by family, and then selecting matching marchers is straightforward and easy to follow. The updatedaria-label="Select families"also aligns with the control’s purpose.
171-221: Fix remaining aria-label and consider distinguishing “no tags” from “not yet loaded”TagSelector still uses
aria-label="Select sections", which is misleading for screen readers and was flagged previously; DrillPrefixSelector and FamilySelector have been corrected, so this one just needs to be brought in line.Also, as written, when tags haven’t finished loading yet, the UI will show “noTagsToSelect”, which conflates “no data” with “still loading”. If you care about that distinction, you could gate the empty-state message behind a separate “loaded & empty” condition.
Minimal aria-label fix:
- return ( - <ToolbarSection aria-label="Select sections"> + return ( + <ToolbarSection aria-label="Select tags">
🧹 Nitpick comments (3)
apps/desktop/src/components/toolbar/tabs/SelectTab.tsx (2)
44-71: Use functional state updates in shift-select to avoid stale selection and simplify depsThe shift-select branch closes over
selectedMarchersand includes it in the dependency array, which is fine but a bit brittle. Using a functional update removes that dependency and avoids any future stale state surprises if the context becomes more complex.You could refactor like this:
- const selectMarcherIds = useCallback( - (e: React.MouseEvent<HTMLButtonElement>, ids: number[]) => { + const selectMarcherIds = useCallback( + (e: React.MouseEvent<HTMLButtonElement>, ids: number[]) => { e.preventDefault(); const newSelectedMarchers = allMarchers.filter((marcher) => ids.includes(marcher.id), ); // if holding shift, add to selection if (e.shiftKey) { - const currentlySelectedIds = new Set( - selectedMarchers.map((marcher) => marcher.id), - ); - const newSelectedMarchersToAdd = newSelectedMarchers.filter( - (marcher) => !currentlySelectedIds.has(marcher.id), - ); - setSelectedMarchers([ - ...selectedMarchers, - ...newSelectedMarchersToAdd, - ]); + setSelectedMarchers((prev) => { + const currentlySelectedIds = new Set( + prev.map((marcher) => marcher.id), + ); + const newSelectedMarchersToAdd = + newSelectedMarchers.filter( + (marcher) => !currentlySelectedIds.has(marcher.id), + ); + return [...prev, ...newSelectedMarchersToAdd]; + }); } else { setSelectedMarchers(newSelectedMarchers); } }, - [allMarchers, selectedMarchers, setSelectedMarchers], + [allMarchers, setSelectedMarchers], );
314-361: Consider guarding localStorage access and localizing the loading messageTwo small polish points in
SelectTabContents:
- The initial
localStorage.getItemcall in theuseStateinitializer assumes a browser-like environment. If this component is ever reused outside the desktop context (e.g., SSR/tests withoutlocalStorage), a try/catch ortypeof window !== "undefined"guard would make it more robust.- The
"Loading..."string is hard-coded, whereas the rest of the file usesTfor i18n. If you already have a shared loading key, wiring it through here would keep localization consistent.Both are optional, but worth considering for future reuse.
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx (1)
95-95: Consider error handling for async operation.The fire-and-forget pattern (
void) could silently swallow errors, leaving the UI in an inconsistent state if the tag appearance creation or scrolling fails.Consider adding error handling:
- void checkAndCreateTagAppearance(); + checkAndCreateTagAppearance().catch((error) => { + console.error('Failed to create/scroll to tag appearance:', error); + // Optionally show a toast notification + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx(1 hunks)apps/desktop/src/components/toolbar/tabs/SelectTab.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Co-locate UI utility helper functions with their components
Always use absolute paths (with@/alias forsrc/directory) for file operations, never relative paths
Batch UI refresh operations where possible for performance optimizationWhen you need to use icons, use @phosphor-icons/react
Files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxapps/desktop/src/components/toolbar/tabs/SelectTab.tsx
🧠 Learnings (8)
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/hooks/queries/index.ts : Update `apps/desktop/src/hooks/queries/index.ts` to export all new React Query hook files
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxapps/desktop/src/components/toolbar/tabs/SelectTab.tsx
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/hooks/queries/use*.ts : React Query mutation options must use selective cache invalidation by invalidating specific query keys rather than blanket invalidations where possible
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/use*.ts?(x) : Consider memoization for expensive calculations within data transformations in multi-query hooks
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/hooks/queries/use*.ts : React Query mutation options must include `onSuccess` handlers for smart cache invalidation and `onError` handlers with `conToastError` for error notification
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/hooks/queries/use*.ts : Create a React Query hook file in `apps/desktop/src/hooks/queries/use[TableName].ts` when adding new database table interactions
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
📚 Learning: 2025-11-27T02:04:17.663Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/multi-query-hooks.mdc:0-0
Timestamp: 2025-11-27T02:04:17.663Z
Learning: Applies to src/hooks/use*.ts?(x) : Avoid inline combine functions in `useQueries` as they cause unnecessary re-renders
Applied to files:
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx
📚 Learning: 2025-11-27T02:03:46.836Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/basic-database-interaction.mdc:0-0
Timestamp: 2025-11-27T02:03:46.836Z
Learning: Applies to apps/desktop/src/db-functions/index.ts : Update `apps/desktop/src/db-functions/index.ts` to export all new database function files
Applied to files:
apps/desktop/src/components/toolbar/tabs/SelectTab.tsx
📚 Learning: 2025-11-27T02:04:23.301Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T02:04:23.301Z
Learning: Applies to packages/**/*.{ts,tsx} : Prefer to use components in './packages/ui/src/components/base' folder for common items like buttons and inputs
Applied to files:
apps/desktop/src/components/toolbar/tabs/SelectTab.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: build-and-release (ubuntu-24.04-arm)
- GitHub Check: desktop-e2e
- GitHub Check: build-and-release (macos-latest)
- GitHub Check: build-and-release (macos-15-intel)
- GitHub Check: build-and-release (windows-latest)
- GitHub Check: build-and-release (ubuntu-latest)
- GitHub Check: test
- GitHub Check: build
🔇 Additional comments (7)
apps/desktop/src/components/toolbar/tabs/SelectTab.tsx (3)
21-42: ViewTab wrapper and helper components look clean and straightforward
ViewTab,Separator, andsectionsFromMarchersare simple, readable, and do what they advertise; deriving sections via aSet+sortgives a stable, deduped list. No changes needed here.
76-122: SelectByPopover implementation is solidPopover wiring, state handling, and translation keys are all wired cleanly, and mapping
SelectByOptionsdirectly into the menu keeps the mode list single-sourced. Looks good as-is.
124-169: SectionSelector logic and empty state behave as expectedDeriving sections from marchers, selecting by section, and the “noSectionsToSelect” fallback all look correct and consistent with the rest of the toolbar UX. No changes needed here.
apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx (4)
1-47: LGTM: Proper setup with memoization.The component setup follows best practices:
- Uses @phosphor-icons/react as per guidelines
- Absolute imports with @/ alias
- Proper memoization of the tag names map to avoid unnecessary recalculations
147-187: LGTM: Proper React patterns.The component setup demonstrates good practices:
- ForwardRef with proper TypeScript typing
- useMemo for expensive operations (sorting, Set creation)
- Appropriate ref and state management for scroll and highlight features
189-226: LGTM: Proper cleanup and timing logic.The scroll and highlight effect is well-implemented:
- Proper guard clauses
- Both timers are stored and cleaned up correctly (addresses past review feedback)
- Smooth scrolling with appropriate delays for UX
228-337: LGTM: Well-structured UI with proper accessibility.The render logic demonstrates good practices:
- Proper use of
Dropdown.Portalfor z-index management- Accessibility with
aria-labelattributes- Disabled state for already-taken tags
- Smooth transitions for highlighting
- Proper ref management for scroll targets
| if (!existingAppearance) { | ||
| // Create the tag appearance and wait for persistence | ||
| await createTagAppearances([ | ||
| { tag_id: targetTagId, start_page_id: targetPageId }, | ||
| ]); | ||
| // Allow React Query to invalidate and re-render | ||
| await queryClient.invalidateQueries(); | ||
| } else { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the file to see the context around lines 67-74
wc -l apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxRepository: OpenMarch/OpenMarch
Length of output: 135
🏁 Script executed:
# Read lines 50-110 to see the full useEffect context
sed -n '50,110p' apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxRepository: OpenMarch/OpenMarch
Length of output: 2248
🏁 Script executed:
# Search for createTagAppearances hook
rg -A 15 "createTagAppearances" apps/desktop/src/hooks --type ts --type tsxRepository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Search for createTagAppearancesMutationOptions or similar
rg "createTagAppearances.*[Mm]utation|mutation.*createTagAppearances" apps/desktop/src --type ts --type tsx -A 5Repository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Find the file containing createTagAppearances mutation
fd "use.*[Tt]ag" apps/desktop/src/hooks/queries --type fRepository: OpenMarch/OpenMarch
Length of output: 45
🏁 Script executed:
# List hooks directory
find apps/desktop/src/hooks -type f -name "*.ts" -o -name "*.tsx" | head -20Repository: OpenMarch/OpenMarch
Length of output: 1039
🏁 Script executed:
# Check imports in TagAppearanceList.tsx
head -50 apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx | grep -E "import|from"Repository: OpenMarch/OpenMarch
Length of output: 661
🏁 Script executed:
# Check full imports section
sed -n '1,30p' apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxRepository: OpenMarch/OpenMarch
Length of output: 1124
🏁 Script executed:
# Search for createTagAppearances definition across the entire codebase
rg "const createTagAppearances|function createTagAppearances|export.*createTagAppearances" apps/desktop/src --type ts --type tsx -B 2 -A 10Repository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Search more broadly for where createTagAppearances comes from
rg "createTagAppearances" apps/desktop/src --type ts --type tsx -lRepository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Look at the full file to understand the structure
cat apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxRepository: OpenMarch/OpenMarch
Length of output: 14128
🏁 Script executed:
# Find useCreateTagAppearances
rg "useCreateTagAppearances|export.*createTagAppearances" apps/desktop/src --type ts --type tsx -lRepository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Search for the hook definition
rg -A 30 "export.*useCreateTagAppearances" apps/desktop/src --type ts --type tsxRepository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Try a broader search
rg "useCreateTagAppearances" apps/desktop/src --type ts --type tsx -A 20 | head -60Repository: OpenMarch/OpenMarch
Length of output: 90
🏁 Script executed:
# Find all files in hooks directory
find apps/desktop/src/hooks -type fRepository: OpenMarch/OpenMarch
Length of output: 1285
🏁 Script executed:
# Check useTagAppearances or related files
ls -la apps/desktop/src/hooks/mutations/ 2>/dev/null || ls -la apps/desktop/src/hooks/Repository: OpenMarch/OpenMarch
Length of output: 556
🏁 Script executed:
# List all hook files
find apps/desktop/src/hooks -name "*.ts" -o -name "*.tsx"Repository: OpenMarch/OpenMarch
Length of output: 1252
🏁 Script executed:
# Search in the entire apps/desktop directory for TagAppearance-related hooks
find apps/desktop/src -name "*[Tt]ag*" -type f | grep -i hookRepository: OpenMarch/OpenMarch
Length of output: 45
🏁 Script executed:
# Look at directory structure
ls -la apps/desktop/src/hooks/Repository: OpenMarch/OpenMarch
Length of output: 556
🏁 Script executed:
# Search in the specific query hooks directory
ls -la apps/desktop/src/hooks/queries/Repository: OpenMarch/OpenMarch
Length of output: 1495
🏁 Script executed:
# Check useTagAppearances file
cat apps/desktop/src/hooks/queries/useTagAppearances.ts 2>/dev/null || echo "File not found, searching..."Repository: OpenMarch/OpenMarch
Length of output: 91
🏁 Script executed:
# List query files
ls -1 apps/desktop/src/hooks/queries/*.ts | head -20Repository: OpenMarch/OpenMarch
Length of output: 924
🏁 Script executed:
# Search for files containing TagAppearance
rg "TagAppearance" apps/desktop/src/hooks --type ts -lRepository: OpenMarch/OpenMarch
Length of output: 215
🏁 Script executed:
# Check if it's in a different location
rg "useCreateTagAppearances\|createTagAppearancesMutationOptions" apps --type ts -lRepository: OpenMarch/OpenMarch
Length of output: 45
🏁 Script executed:
# Broader search across all TypeScript files
rg "useCreateTagAppearances" . --type ts -A 20 2>/dev/null | head -100Repository: OpenMarch/OpenMarch
Length of output: 45
🏁 Script executed:
# Search for query key factories or tag appearance query keys
rg "tagAppearance|TagAppearance" apps/desktop/src/hooks/queries --type ts -A 3 | head -80Repository: OpenMarch/OpenMarch
Length of output: 6321
🏁 Script executed:
# Search for query key definitions
rg "queryKey.*=.*\[|queryOptions" apps/desktop/src/hooks/queries --type ts | head -30Repository: OpenMarch/OpenMarch
Length of output: 2806
🏁 Script executed:
# Check if there's a query key factory file
find apps/desktop/src -name "*key*" -o -name "*factory*" | grep -i queryRepository: OpenMarch/OpenMarch
Length of output: 45
🏁 Script executed:
# Look for invalidateQueries usage patterns in the codebase
rg "invalidateQueries" apps/desktop/src --type ts -B 2 -A 2 | head -100Repository: OpenMarch/OpenMarch
Length of output: 7470
🏁 Script executed:
# Double-check the exact line 73 content to ensure accuracy
sed -n '67,75p' apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsxRepository: OpenMarch/OpenMarch
Length of output: 524
Replace blanket query invalidation with selective key-based invalidation.
The queryClient.invalidateQueries() call on line 73 without arguments invalidates all cached queries, causing unnecessary re-fetches across the entire app. Project patterns use selective invalidation with specific query keys (see sharedInvalidators.ts). Either remove this manual invalidation and ensure the mutation options handle cache updates via onSuccess handlers, or replace it with selective invalidation targeting affected query keys like tag appearances:
if (!existingAppearance) {
// Create the tag appearance and wait for persistence
await createTagAppearances([
{ tag_id: targetTagId, start_page_id: targetPageId },
]);
- // Allow React Query to invalidate and re-render
- await queryClient.invalidateQueries();
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!existingAppearance) { | |
| // Create the tag appearance and wait for persistence | |
| await createTagAppearances([ | |
| { tag_id: targetTagId, start_page_id: targetPageId }, | |
| ]); | |
| // Allow React Query to invalidate and re-render | |
| await queryClient.invalidateQueries(); | |
| } else { | |
| if (!existingAppearance) { | |
| // Create the tag appearance and wait for persistence | |
| await createTagAppearances([ | |
| { tag_id: targetTagId, start_page_id: targetPageId }, | |
| ]); | |
| } else { |
🤖 Prompt for AI Agents
In apps/desktop/src/components/marcher/appearance/TagAppearanceList.tsx around
lines 67 to 74, the code calls queryClient.invalidateQueries() with no args
which invalidates all queries; replace this with selective invalidation (or
remove manual invalidation and rely on the mutation's onSuccess). Specifically,
import and use the project's shared invalidator for tag appearances (e.g.
sharedInvalidators.tagAppearances or the equivalent key) or call
queryClient.invalidateQueries with the specific key for the affected data (for
example ['tagAppearances', targetTagId] or ['tagAppearances', targetPageId] as
appropriate), and/or move the cache update into the createTagAppearances
mutation’s onSuccess handler to update or invalidate only the relevant keys.
| handleCreateAppearance={(tagId) => | ||
| void createTagAppearances([ | ||
| { tag_id: tagId, start_page_id: page.id }, | ||
| ]) | ||
| } | ||
| handleUpdateAppearance={(modifiedAppearance) => | ||
| void updateTagAppearances([modifiedAppearance]) | ||
| } | ||
| handleDeleteAppearance={(tagId) => | ||
| void deleteTagAppearances(new Set([tagId])) | ||
| } |
There was a problem hiding this comment.
Fix naming inconsistency and add error handling.
Two issues:
-
Naming inconsistency: The
handleDeleteAppearanceparameter is namedtagId(line 156), but it actually receivestagAppearance.id(line 321), which is the appearance ID, not the tag ID. This is misleading and could cause confusion or bugs. -
Fire-and-forget mutations: All three handlers use
voidwith no error handling. If these mutations fail, the user gets no feedback.
Apply this diff to fix the naming:
- handleDeleteAppearance={(tagId) =>
- void deleteTagAppearances(new Set([tagId]))
+ handleDeleteAppearance={(appearanceId) =>
+ void deleteTagAppearances(new Set([appearanceId]))
}And update the type definition on line 156:
- handleDeleteAppearance: (tagId: number) => void;
+ handleDeleteAppearance: (appearanceId: number) => void;For error handling, ensure the mutation options include onError handlers, or add .catch() handlers here:
handleCreateAppearance={(tagId) =>
- void createTagAppearances([
+ createTagAppearances([
{ tag_id: tagId, start_page_id: page.id },
- ])
+ ]).catch((error) => {
+ console.error('Failed to create tag appearance:', error);
+ })
}Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/desktop/e2e/tests/1-basic-render.spec.mts (3)
1-1: Prefer a type-only import forPageSince
Pageis only used as a type, consider switching to a type-only import to keep runtime imports minimal and make intent explicit:-import { expect, Page } from "@playwright/test"; +import { expect } from "@playwright/test"; +import type { Page } from "@playwright/test";This keeps the runtime dependency surface a bit cleaner while preserving typings.
37-40: Good reuse viacanvasIsVisible; consider making the locators more stableCentralizing the canvas/timeline visibility checks in
canvasIsVisible(page)is a nice cleanup and makes the intent of the tests clearer. The only concern is potential flakiness from:
page.getByText("Timeline")if the label text changes or appears in multiple places.page.locator("canvas").nth(1)if the DOM adds/removes/reorders<canvas>elements.Not a blocker, but if these tests start to flicker, it may be worth switching to a more stable locator (e.g., role,
data-testid, or a more specific container for the main canvas) while keeping this helper as the single place to update it.Also applies to: 51-52, 56-57
93-94:nth(1)on the Marchers workspace locator may be brittle
page.locator("#workspace div").filter({ hasText: "Marchers" }).nth(1)assumes a specific ordering/count of matching divs. If the layout or copy changes, this test could start failing even though the UI is still correct.If possible, consider targeting a more explicit element (role/heading/region or a dedicated test id) so you don't depend on the second matching div:
- e.g., a region/section with accessible name “Marchers”, or
- a specific container element with a stable selector.
This would make the “Sidebars are visible” test more robust to UI tweaks.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/desktop/e2e/tests/1-basic-render.spec.mts(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-27T02:03:30.741Z
Learnt from: CR
Repo: OpenMarch/OpenMarch PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T02:03:30.741Z
Learning: Use Vitest for unit tests and Playwright for e2e tests
Applied to files:
apps/desktop/e2e/tests/1-basic-render.spec.mts
🧬 Code graph analysis (1)
apps/desktop/e2e/tests/1-basic-render.spec.mts (1)
apps/desktop/e2e/fixtures.mts (1)
test(105-206)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: build-and-release (ubuntu-latest)
- GitHub Check: build-and-release (ubuntu-24.04-arm)
- GitHub Check: build-and-release (windows-latest)
- GitHub Check: build-and-release (macos-15-intel)
- GitHub Check: build-and-release (macos-latest)
- GitHub Check: pr-checks (build)
- GitHub Check: desktop-e2e
- GitHub Check: build
- GitHub Check: test
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.