Skip to content

fix : File/Folder path swimlanes - key by full path, with a readable, pinnable header | #839#845

Open
NikolayYakovlev wants to merge 6 commits into
tu2-atmanand:mainfrom
NikolayYakovlev:fix/839-swimlane-path-keying
Open

fix : File/Folder path swimlanes - key by full path, with a readable, pinnable header | #839#845
NikolayYakovlev wants to merge 6 commits into
tu2-atmanand:mainfrom
NikolayYakovlev:fix/839-swimlane-path-keying

Conversation

@NikolayYakovlev

Copy link
Copy Markdown

Fixes #839. Swimlanes grouped by File path or Folder path were keyed by the leaf name (the file or folder name), so identically-named files/folders in different locations (several TODO.md, or a docs/ folder under each project) showed up as separate but indistinguishable lanes with identical headers, and shared a single collapse state - minimizing one minimized all of them.

They are now keyed by the full vault-relative path, and the header shows that path. Showing the full path surfaced several pre-existing header issues (the header was built for short leaf names), so this PR also makes the header readable and usable: it no longer clips the file name off the end, sizes correctly to the columns, offers a truncation mode, keeps a collapsed lane's header from stretching across the whole board, and pins the header to the left on horizontal scroll.

What changed

  1. fix : key File/Folder path swimlanes by full path, not leaf name - the core fix. Both the collapse state and the header use the full swimlaneValue instead of the leaf swimlaneName, so same-named files/folders become distinct lanes and collapse independently.
  2. fix : swimlane header layout - keep full path visible, size to columns width - keeps the full path from being clipped too early (flex min-width) and sizes the header to the columns' width via contain: inline-size (replacing a dependency on a CSS variable that isn't reliably set).
  3. feat : selectable path truncation for File/Folder swimlane headers (start/middle/end) - a headerTruncation option so long paths stay readable. Default middle (keeps the path root and the last segments, so the file name stays visible); start keeps the tail; end is the classic trailing ellipsis. Applies only to path swimlanes.
  4. fix : vertical swimlane header height follows row content, not fixed 125px - the rotated (vertical) header spans the row's real height instead of a hard-coded 125px.
  5. fix : collapsed swimlane header no longer stretches to the full board width - a minimized lane rendered no columns, so its full-width header stretched across the whole board; it now matches the columns' width.
  6. fix : swimlane header now pins to the left on horizontal scroll (sticky never engaged before) - the header carried position: sticky but filled its row, so it had no room to travel and scrolled away. The inner label/count group is made narrow so sticky engages: the label pins to the left while the full-width header bar scrolls with the columns. Works in both the horizontal and vertical header layouts.

Before / after

Recorded on stock 1.10.11 (before) and this branch (after):

  • File path swimlanes - before: several lanes titled TODO.md, indistinguishable, collapsing one collapses all (shared state); after: full vault paths, distinct lanes, independent collapse.
  • Folder path swimlanes - before: same-named docs folders get identical, indistinguishable headers and share a collapse state (same as File path); after: distinct full folder paths.
  • Header truncation - the three modes (start / middle / end) on a deep path.
  • Collapsed lane header - before: the header of a minimized lane stretches across the whole board; after: it matches the columns' width.
  • Sticky header - before: the header scrolls away when you scroll the columns horizontally; after: the label/count pin to the left while the columns scroll (both horizontal and vertical layouts).
2026.07.08.16.24.57.mp4
2026.07.08.19.27.46.2.mp4

Notes

  • Truncation applies only to File path / Folder path swimlanes; tag/status/priority swimlanes are unchanged. Default is middle (keeps the file name visible) - happy to change the default if you prefer.
  • The header now shows the full path; title= (tooltip) already carried it.
  • Header pinning restores what position: sticky was already meant to do; there is no new setting - it is always on.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Fix path swimlane keying; add truncation + sticky, correctly sized headers

🐞 Bug fix ✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Key file/folder swimlanes and collapse state by full vault-relative path.
• Make swimlane headers readable: correct sizing, sticky label, and proper collapsed width.
• Add configurable path truncation modes (start/middle/end) for path-based swimlanes.
Diagram

graph TD
  UI["Kanban board view"] --> KSC["KanbanSwimlanesContainer"] --> CFG[("board.swimlanes config")]
  CFG --> SCM["SwimlanesConfigModal"] --> LOC["en locale strings"]
  CFG --> BCM["BoardConfigModal"]
  KSC --> RO{{"ResizeObserver"}} --> MEAS["Row/width metrics"]
  KSC --> CSS["Swimlane header CSS"]

  subgraph Legend
    direction LR
    _ui["UI / React"] ~~~ _cfg[("Settings data")] ~~~ _ext{{"Browser API"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Key minimized swimlanes by stable hash/id instead of raw path
  • ➕ Avoids long strings in settings payload
  • ➕ Potentially resilient to future display/format changes
  • ➖ Harder to debug/edit settings manually
  • ➖ Still requires full-path display to disambiguate headers
  • ➖ Requires migration strategy for existing minimized arrays
2. Pure-CSS header sizing/truncation (avoid ResizeObserver)
  • ➕ Less runtime complexity and fewer observers
  • ➕ Lower risk of observer lifecycle bugs
  • ➖ Hard to reliably size vertical rotated headers to dynamic row heights
  • ➖ Collapsed-row width matching likely remains incorrect without measurement
  • ➖ Popout/window-specific observer note suggests real-world constraints
3. Store minimized state keyed by (property + value) tuple
  • ➕ Prevents collisions if different properties share identical values
  • ➕ Clearer semantics in settings
  • ➖ Larger settings changes and migration needed
  • ➖ Primary issue is path leaf collisions; full path already resolves it

Recommendation: Current approach (key by full vault-relative path, render full path, and add measured layout fixes) is the most user-transparent and directly addresses the collision + indistinguishable header problem. ResizeObserver usage is justified for vertical-header height and collapsed-row width parity; consider noting that existing minimized entries keyed by leaf name won’t match after upgrade (if backward compatibility/migration matters).

Files changed (9) +184 / -35

Enhancement (2) +35 / -1
Enums.tsAdd HeaderTruncationOptions enum +6/-0

Add HeaderTruncationOptions enum

• Introduces HeaderTruncationOptions (start/middle/end) used by rendering and settings UI.

src/interfaces/Enums.ts

SwimlanesConfigModal.tsxAdd swimlane header truncation dropdown to configuration modal +29/-1

Add swimlane header truncation dropdown to configuration modal

• Wires headerTruncation into the swimlane configuration modal state and save payload. Adds a dropdown with localized labels to choose start/middle/end truncation modes.

src/modals/SwimlanesConfigModal.tsx

Bug fix (2) +131 / -32
KanbanSwimlanesContainer.tsxKey path swimlanes by full value; add truncation + runtime header measurements +85/-21

Key path swimlanes by full value; add truncation + runtime header measurements

• Swimlane naming for filePath/folderPath now uses the full vault-relative path and minimized state is keyed by the full swimlaneValue to prevent collisions. Adds configurable header truncation rendering (start/middle/end) and uses ResizeObserver/useLayoutEffect to measure row heights (vertical headers) and column-header width (collapsed rows) so headers size and stick correctly.

src/components/KanbanView/KanbanSwimlanesContainer.tsx

styles.cssFix swimlane header sticky/sizing and add truncation CSS modes +46/-11

Fix swimlane header sticky/sizing and add truncation CSS modes

• Adjusts swimlane header flex behavior so sticky positioning engages and labels don’t clip prematurely; uses contain:inline-size and min-width/overflow fixes. Adds CSS rules for start (RTL) and middle (split head/tail) truncation and prevents counters/icons from shrinking; improves minimized vertical header sticky behavior.

styles.css

Documentation (2) +10 / -0
en.jsonAdd localization strings for header truncation setting +5/-0

Add localization strings for header truncation setting

• Adds English strings for the new swimlane header truncation setting and its options.

src/utils/lang/locale/en.json

en.tsAdd TypeScript locale entries for header truncation setting +5/-0

Add TypeScript locale entries for header truncation setting

• Mirrors the new English truncation strings in the TS locale map.

src/utils/lang/locale/en.ts

Other (3) +8 / -2
BoardConfigs.tsExtend swimlane config with headerTruncation option +1/-0

Extend swimlane config with headerTruncation option

• Adds a headerTruncation field to swimlaneConfigs so truncation mode can be persisted per board.

src/interfaces/BoardConfigs.ts

GlobalSettings.tsDefault swimlane settings now include middle truncation +4/-0

Default swimlane settings now include middle truncation

• Updates DEFAULT_SETTINGS to set swimlanes.headerTruncation to middle for default/example boards.

src/interfaces/GlobalSettings.ts

BoardConfigModal.tsxInitialize new boards with headerTruncation default +3/-2

Initialize new boards with headerTruncation default

• When creating a new board, swimlane defaults now include headerTruncation: middle.

src/modals/BoardConfigModal.tsx

@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used

Grey Divider


Remediation recommended

1. Minimize key breaks saved state ✓ Resolved 🐞 Bug ☼ Reliability
Description
Swimlane minimized state is now checked and stored by swimlaneValue/swimlaneItemValue instead of
the displayed swimlaneName, so previously-saved minimized lanes (e.g. status lanes where
swimlaneName is a mapped label like “In Progress”, and the “No tags” lane) will no longer match
after upgrade. This can make existing minimized states appear “lost” and can leave legacy entries in
minimized that will never be toggled off by the new logic.
Code

src/components/KanbanView/KanbanSwimlanesContainer.tsx[R224-229]

+			} else if (property === 'filePath' || property === 'folderPath') {
+				// #839: show the full vault-relative path so identically-named files/folders stay distinct
+				swimlaneName = swimlaneItemValue;
			}
-			const isSwimlaneMinimized = minimized?.includes(swimlaneName) ?? false;
+			const isSwimlaneMinimized = minimized?.includes(swimlaneItemValue) ?? false;
Evidence
swimlaneName is derived from swimlaneValue for some properties (status mapping and “No tags”),
but minimization is now keyed strictly by the raw value, so older minimized arrays (stored by
name) will not match. The repository’s settings type comment also indicates minimized stores
“names”, reinforcing that the old persisted key was the display label.

src/components/KanbanView/KanbanSwimlanesContainer.tsx[218-235]
src/components/KanbanView/KanbanSwimlanesContainer.tsx[341-352]
src/interfaces/BoardConfigs.ts[112-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Swimlane collapse state persistence changed from using the display label (`swimlaneName`) to using the raw grouping value (`swimlaneValue`) for *all* properties. Existing saved boards likely have `swimlanes.minimized` entries keyed by the old `swimlaneName` (e.g., mapped status name / “No tags”), which won’t match anymore.

### Issue Context
- `swimlaneName` can differ from `swimlaneValue` for `status` (mapped name) and empty `tags` (“No tags”).
- The UI now checks `minimized.includes(swimlaneItemValue)` and toggles `arr.indexOf(swimlaneValue)`.

### Fix Focus Areas
- src/components/KanbanView/KanbanSwimlanesContainer.tsx[218-235]
- src/components/KanbanView/KanbanSwimlanesContainer.tsx[341-352]
- src/interfaces/BoardConfigs.ts[112-127]

### Suggested fix approach
1. Preserve backward compatibility by reading **both** keys when deciding minimized state:
  - `isMinimized = minimized.includes(swimlaneValue) || minimized.includes(swimlaneName)`
2. When toggling, remove both representations to avoid “stuck” legacy entries:
  - If minimizing: add the new canonical key.
  - If unminimizing: remove any matching `swimlaneValue` *and* `swimlaneName` entries.
3. Optionally, choose canonical key per property:
  - For `filePath`/`folderPath`: canonical key should be the full vault-relative path (`swimlaneValue`).
  - For other properties: consider keeping canonical key as `swimlaneName` to avoid unnecessary breaking changes, or add a one-time migration on load.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Popout ResizeObserver mismatch ✓ Resolved 🐞 Bug ☼ Reliability
Description
The vertical-header row-height measurement uses the global ResizeObserver, but the same file
already notes that ResizeObserver must be constructed from the observed element’s
ownerDocument.defaultView to work in popouts. In popouts, this can prevent row-height updates,
leaving vertical headers stuck at the CSS fallback sizing rather than matching the row’s real
height.
Code

src/components/KanbanView/KanbanSwimlanesContainer.tsx[R249-267]

+	useEffect(() => {
+		if (headerUIType !== HeaderUITypeOptions.vertical) return;
+		const observer = new ResizeObserver((entries) => {
+			setSwimlaneRowHeights((prev) => {
+				const next = { ...prev };
+				let changed = false;
+				for (const entry of entries) {
+					const rowIndex = Number((entry.target as HTMLElement).dataset.rowIndex);
+					const height = entry.contentRect.height;
+					if (next[rowIndex] !== height) {
+						next[rowIndex] = height;
+						changed = true;
+					}
+				}
+				return changed ? next : prev;
+			});
+		});
+		columnsWrapperRefs.current.forEach((el) => observer.observe(el));
+		return () => observer.disconnect();
Evidence
The row-height observer is created from the global window, while the column-header observer
explicitly uses ownerDocument.defaultView to work in popouts. If the row-height observer doesn’t
fire, swimlaneRowHeights won’t update and the vertical header keeps its CSS fallback width (125px)
instead of sizing to the real row height.

src/components/KanbanView/KanbanSwimlanesContainer.tsx[248-268]
src/components/KanbanView/KanbanSwimlanesContainer.tsx[270-279]
src/components/KanbanView/KanbanSwimlanesContainer.tsx[431-433]
styles.css[6983-7000]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The vertical row-height measurement effect constructs `new ResizeObserver(...)` using the global constructor, but popout boards can live in a different `window` than the plugin’s global context.

### Issue Context
This file already handles the popout case for the column-header observer by constructing it from `el.ownerDocument.defaultView`.

### Fix Focus Areas
- src/components/KanbanView/KanbanSwimlanesContainer.tsx[248-268]
- src/components/KanbanView/KanbanSwimlanesContainer.tsx[270-281]
- src/components/KanbanView/KanbanSwimlanesContainer.tsx[431-433]
- styles.css[6983-7000]

### Suggested fix approach
1. In the row-height `useEffect`, pick a window from one of the observed elements:
  - e.g. `const firstEl = columnsWrapperRefs.current.values().next().value;`
  - `const win = firstEl?.ownerDocument?.defaultView ?? window;`
2. Construct observer via that window:
  - `const RO = win.ResizeObserver ?? ResizeObserver;`
  - `const observer = new RO(callback);`
3. Keep the existing disconnect cleanup.

This makes the vertical header sizing reliable in popouts, matching the pattern already used for `columnsHeaderRef`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/components/KanbanView/KanbanSwimlanesContainer.tsx
Comment thread src/components/KanbanView/KanbanSwimlanesContainer.tsx
Nikolay Yakovlev added 6 commits July 8, 2026 20:36
…s width | tu2-atmanand#839

- counter stays after the name, overflow clipped, value no longer collapses (flex min-width)
- size the horizontal header to the columns' width via contain:inline-size (scrolls with them)
…tart/middle/end) | tu2-atmanand#839

Adds a headerTruncation option (start / middle / end) so the full vault path stays readable. Default middle: keeps the path root and last 3 segments (distinguishes same-named files; folder tails keep the trailing slash).
…125px | tu2-atmanand#839

Measure each row with a ResizeObserver so the rotated header spans the row's real height instead of a hard-coded 125px.
… width | tu2-atmanand#839

A minimized swimlane renders no columns, so its header stretched to the full container instead of the columns' width - in both the horizontal and vertical header layouts. Measure the always-rendered column-header width and apply it to collapsed rows so they match the expanded ones. The ResizeObserver is created in the header element's own window, so the measurement also works when the board is in a popout window.
… spacing | tu2-atmanand#839

The header carries position:sticky but filled its row (flex:1), so it had no room to travel and scrolled away with the columns. Size the inner label/count group to its content (flex: 0 1 auto) instead of filling the row, so sticky engages: the label and count pin to the left while the full-width bar scrolls with the columns. The group shrinks to the bar and truncates only at its edge, so the label uses all the available width rather than a fixed fraction. Both the horizontal and vertical collapsed headers.

Also align the collapsed header spacing so the horizontal and vertical layouts match: equal label-to-value (0.5rem) and value-to-count (1rem) gaps, and the chevron flush to the label (the vertical layout previously used one flex gap that also spread the chevron away).
@NikolayYakovlev
NikolayYakovlev force-pushed the fix/839-swimlane-path-keying branch from 34c8ff4 to c57fa74 Compare July 8, 2026 15:49
@tu2-atmanand tu2-atmanand added feature New feature request enhancement An existing feature can be enhanced/improved. labels Jul 8, 2026
@github-project-automation github-project-automation Bot moved this to Backlogs in Task Board Dev Jul 8, 2026
@tu2-atmanand tu2-atmanand added the brainstorm These issue/feat needs to be discussed and have to find the solution label Jul 8, 2026
@tu2-atmanand

Copy link
Copy Markdown
Owner

Hi @NikolayYakovlev !

Thank you very much for joining the development of this plugin project!

I have not gone through the whole PR yet, just saw the video (thanks for sharing that and testing all possible cases), the suggested solution looks promising to me. I was wondering about the problem you have raised in the #839 but couldnt able to find a better solution earlier. Your solution might be the right way to go about it.

I am actually very busy these days, and not sure, if I will going to get much time to get active again in this project any time soon. But, will find some time to finish off some of the very important things on the roadmap.

Actually, I was supposed to release the next series of this plugin, 2.0.0. This new series is a complete redesign of the architecture of the plugin. But, your contributions will still hold good, only we might need to change the target branch to this one : https://github.com/tu2-atmanand/Task-Board/tree/release-v2.0.0

In next few weeks, Ill find some time to release the 2.0.0 version of this plugin and will then come back in this PR.

I have also seen your another PR, that PR might not be required, as I believe 2.0.0 has already fixed that problem. But lets see. Thanks again for your contribution!

@NikolayYakovlev

Copy link
Copy Markdown
Author

Hi @tu2-atmanand, thanks a lot for the kind welcome, and for building Task Board in the first place.

A few weeks ago I realized I could no longer keep my tasks under control. I keep my knowledge base in Obsidian, and separately a Git repo full of small utility projects, each with its own docs and its own task list with different levels of importance, urgency, and so on. It became clear I needed a Kanban board, but one that could gather tasks from many files. After some searching I narrowed it down to a few main candidates and looked into what each could do. Your plugin turned out to be the best fit: one place that pulls together tasks from across the whole vault, with filtering and exclusion, and support for different sets of columns (I saw PR #583, and honestly I'd like to adapt it for my own use when I find the time, or wait for it to land in main / 2.0.0).

Using it day to day, though, a number of defects surfaced almost immediately, ones that currently keep me from fully relying on it in my setup. My vault has a lot of files, so I hit problems with scanning and with unpredictable task display. Right now the folder I've scoped the board to for a demo actually holds more task files than the board shows: at first the filtering worked, then after a restart the tasks disappeared, then a few minutes later three appeared, then a few more. So the next thing I want to dig into is the scanning mechanism itself, along with clearer status messages.

There's also the board's sizing in a popout window: it can show the board list collapsed into a hamburger menu even when there's room to stretch across the whole screen. As with the filter display in #838, the cause is that it listens to events on the main window rather than the popout.

The list of issues like these keeps growing, but I'm genuinely enthusiastic about it, because nothing else out there comes close to what Task Board already does. As I work out fixes for the rough edges I find, I'll keep opening PRs.

2.0.0 being actively underway, with a fundamentally reworked architecture, is a little daunting, but I'll try to find time to study the differences and will probably move over to the 2.0.0 branch soon, so I don't re-solve problems that are already solved there. And a redesign may well be the only way for a project to keep moving forward; sometimes you have to revisit earlier decisions to meet new demands.

So no problem at all if #840 isn't needed. If 1.10 won't be maintained and 2.0.0 already solves it differently, I'm completely fine with that PR being closed.

Thanks again for the time you've put into this plugin; it's really good. And take your time with 2.0.0.

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

Labels

brainstorm These issue/feat needs to be discussed and have to find the solution enhancement An existing feature can be enhanced/improved. feature New feature request

Projects

Status: Backlogs

Development

Successfully merging this pull request may close these issues.

Swimlanes grouped by "Folder path" are keyed by folder name, not full path (ambiguous headers + shared collapse state for same-named folders)

2 participants