Summary
getClusterColor (src/workbench/image-map/clusterPalette.ts) indexes a 15-entry palette with CLUSTER_PALETTE[cluster % 15], so cluster 0 and cluster 15 render identically, 1 and 16, and so on.
That matters more than usual here: cluster colouring is the entire premise of the widget. Two visually unrelated groups sharing a colour reads as "these are the same cluster", which is precisely the conclusion the map exists to support.
DBSCAN over a real gallery routinely produces more than 15 labels — the backend clusters up to MAX_CLUSTERED_POINTS = 50_000 points with no bound on the label count.
Suggested direction
Generate colours procedurally beyond the palette (e.g. golden-ratio hue rotation in OKLCH) so distinct labels stay distinguishable, or cap the number of coloured clusters and render the tail as a single "other" colour, which at least does not assert a false grouping.
Minor, in the same function
cluster < 0 correctly routes every negative label to the noise colour. A non-integer label would index the palette with a fractional key and yield undefined in marker.color; not reachable today, since the backend declares cluster: int.
Context
Found during adversarial review of PR #38.
Summary
getClusterColor(src/workbench/image-map/clusterPalette.ts) indexes a 15-entry palette withCLUSTER_PALETTE[cluster % 15], so cluster 0 and cluster 15 render identically, 1 and 16, and so on.That matters more than usual here: cluster colouring is the entire premise of the widget. Two visually unrelated groups sharing a colour reads as "these are the same cluster", which is precisely the conclusion the map exists to support.
DBSCAN over a real gallery routinely produces more than 15 labels — the backend clusters up to
MAX_CLUSTERED_POINTS = 50_000points with no bound on the label count.Suggested direction
Generate colours procedurally beyond the palette (e.g. golden-ratio hue rotation in OKLCH) so distinct labels stay distinguishable, or cap the number of coloured clusters and render the tail as a single "other" colour, which at least does not assert a false grouping.
Minor, in the same function
cluster < 0correctly routes every negative label to the noise colour. A non-integer label would index the palette with a fractional key and yieldundefinedinmarker.color; not reachable today, since the backend declarescluster: int.Context
Found during adversarial review of PR #38.