From 8826f86eabb1ead3f3de3e95de88fefe5ef56d65 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:24:18 +0100 Subject: [PATCH 01/13] feat(web): add task tag display preferences --- .../src/hooks/useTaskTagDisplayPreferences.ts | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 apps/web/src/hooks/useTaskTagDisplayPreferences.ts diff --git a/apps/web/src/hooks/useTaskTagDisplayPreferences.ts b/apps/web/src/hooks/useTaskTagDisplayPreferences.ts new file mode 100644 index 0000000..417ce3f --- /dev/null +++ b/apps/web/src/hooks/useTaskTagDisplayPreferences.ts @@ -0,0 +1,118 @@ +'use client' + +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useRoadmapLifecycle } from '@/context/RoadmapContext' + +interface TaskTagDisplayPreferences { + favoriteTagId: string | null + showAutomaticStatus: boolean +} + +interface TaskTagPreferenceEventDetail { + storageKey: string + preferences: TaskTagDisplayPreferences +} + +const STORAGE_PREFIX = 'roadforge.task-tag-display.v1' +const PREFERENCE_EVENT = 'roadforge:task-tag-display-change' +const DEFAULT_PREFERENCES: TaskTagDisplayPreferences = { + favoriteTagId: null, + showAutomaticStatus: true, +} + +export function isAutomaticTaskTag(tagId: string): boolean { + return /^status:/i.test(tagId.trim()) +} + +export function getUserDisplayTags(tagIds: string[]): string[] { + return tagIds.filter((tagId) => !isAutomaticTaskTag(tagId)) +} + +function readPreferences(storageKey: string): TaskTagDisplayPreferences { + if (typeof window === 'undefined') return DEFAULT_PREFERENCES + + try { + const stored = window.localStorage.getItem(storageKey) + if (!stored) return DEFAULT_PREFERENCES + + const parsed = JSON.parse(stored) as Partial + return { + favoriteTagId: typeof parsed.favoriteTagId === 'string' + ? parsed.favoriteTagId + : null, + showAutomaticStatus: parsed.showAutomaticStatus !== false, + } + } catch { + return DEFAULT_PREFERENCES + } +} + +export function useTaskTagDisplayPreferences(taskId: string, tagIds: string[]) { + const { activeRoadmapId } = useRoadmapLifecycle() + const userTags = useMemo(() => getUserDisplayTags(tagIds), [tagIds]) + const storageKey = `${STORAGE_PREFIX}:${activeRoadmapId ?? 'local'}:${taskId}` + const [preferences, setPreferences] = useState( + DEFAULT_PREFERENCES, + ) + + useEffect(() => { + setPreferences(readPreferences(storageKey)) + + const handleStorage = (event: StorageEvent) => { + if (event.key === storageKey) { + setPreferences(readPreferences(storageKey)) + } + } + const handlePreferenceChange = (event: Event) => { + const detail = (event as CustomEvent).detail + if (detail?.storageKey === storageKey) { + setPreferences(detail.preferences) + } + } + + window.addEventListener('storage', handleStorage) + window.addEventListener(PREFERENCE_EVENT, handlePreferenceChange) + return () => { + window.removeEventListener('storage', handleStorage) + window.removeEventListener(PREFERENCE_EVENT, handlePreferenceChange) + } + }, [storageKey]) + + const updatePreferences = useCallback(( + updater: (current: TaskTagDisplayPreferences) => TaskTagDisplayPreferences, + ) => { + setPreferences((current) => { + const next = updater(current) + window.localStorage.setItem(storageKey, JSON.stringify(next)) + window.dispatchEvent(new CustomEvent(PREFERENCE_EVENT, { + detail: { storageKey, preferences: next }, + })) + return next + }) + }, [storageKey]) + + const selectFavoriteTag = useCallback((tagId: string) => { + if (!userTags.includes(tagId)) return + updatePreferences((current) => ({ ...current, favoriteTagId: tagId })) + }, [updatePreferences, userTags]) + + const toggleAutomaticStatus = useCallback(() => { + updatePreferences((current) => ({ + ...current, + showAutomaticStatus: !current.showAutomaticStatus, + })) + }, [updatePreferences]) + + const favoriteTagId = preferences.favoriteTagId + && userTags.includes(preferences.favoriteTagId) + ? preferences.favoriteTagId + : userTags[0] ?? null + + return { + userTags, + favoriteTagId, + showAutomaticStatus: preferences.showAutomaticStatus, + selectFavoriteTag, + toggleAutomaticStatus, + } +} From d482491d62d475cd1d8c76c59670523ae62d4e3d Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:24:46 +0100 Subject: [PATCH 02/13] feat(web): feature one task tag in the header --- .../roadmap/task-row/TaskRowHeader.tsx | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/roadmap/task-row/TaskRowHeader.tsx b/apps/web/src/components/roadmap/task-row/TaskRowHeader.tsx index abb7581..f1afeeb 100644 --- a/apps/web/src/components/roadmap/task-row/TaskRowHeader.tsx +++ b/apps/web/src/components/roadmap/task-row/TaskRowHeader.tsx @@ -2,6 +2,7 @@ import { Icon } from '@/components/ui/Icon' import { TagChip } from '@/components/roadmap/TagChip' +import { useTaskTagDisplayPreferences } from '@/hooks/useTaskTagDisplayPreferences' import { TASK_STATUS_LABELS, type DerivedTaskStatus, @@ -47,6 +48,16 @@ export function TaskRowHeader({ onCheck, onToggle, }: TaskRowHeaderProps) { + const { + userTags, + favoriteTagId, + showAutomaticStatus, + } = useTaskTagDisplayPreferences(task.id, visibleTags) + const remainingTagCount = Math.max( + 0, + userTags.length - (favoriteTagId ? 1 : 0), + ) + return (
{task.title} - - {TASK_STATUS_LABELS[status]} - - {visibleTags.slice(0, 2).map((tagId) => ( + {showAutomaticStatus && ( + + {TASK_STATUS_LABELS[status]} + + )} + {favoriteTagId && ( - ))} - {visibleTags.length > 2 && ( - +{visibleTags.length - 2} + )} + {remainingTagCount > 0 && ( + +{remainingTagCount} )} {lockedByOther && ( From 3f3bc27dd36d8dee629f6e8f00898e11e0f1247a Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:25:15 +0100 Subject: [PATCH 03/13] feat(web): make task tag priority selectable --- .../src/components/roadmap/TaskDetailMeta.tsx | 55 +++++++++++++++++-- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/roadmap/TaskDetailMeta.tsx b/apps/web/src/components/roadmap/TaskDetailMeta.tsx index a48f280..e19c712 100644 --- a/apps/web/src/components/roadmap/TaskDetailMeta.tsx +++ b/apps/web/src/components/roadmap/TaskDetailMeta.tsx @@ -1,5 +1,8 @@ 'use client' +import { Icon } from '@/components/ui/Icon' +import { useTaskTagDisplayPreferences } from '@/hooks/useTaskTagDisplayPreferences' +import { resolveTagDisplay } from '@/lib/tag-registry' import type { Task, TagDefinition } from '@/types/roadmap' import { TagChip } from './TagChip' @@ -18,6 +21,14 @@ export function TaskDetailMeta({ visibleTags, registry = [], }: TaskDetailMetaProps) { + const { + userTags, + favoriteTagId, + showAutomaticStatus, + selectFavoriteTag, + toggleAutomaticStatus, + } = useTaskTagDisplayPreferences(task.id, visibleTags) + return (
{!isNested && ( @@ -44,12 +55,44 @@ export function TaskDetailMeta({
Tags
-
- {visibleTags.length > 0 - ? visibleTags.map((tagId) => ( - - )) - : No tags} +
+ + {userTags.map((tagId) => { + const selected = tagId === favoriteTagId + const label = resolveTagDisplay(tagId, registry).label + return ( + + ) + })} + {userTags.length === 0 && ( + No custom tags + )}
From 7a96840a99cb521a11a0f53a1f25eee79d0589ad Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:25:28 +0100 Subject: [PATCH 04/13] feat(web): label expanded task descriptions --- .../roadmap/MarkdownDescription.tsx | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/web/src/components/roadmap/MarkdownDescription.tsx b/apps/web/src/components/roadmap/MarkdownDescription.tsx index 4c1d766..f71e7e7 100644 --- a/apps/web/src/components/roadmap/MarkdownDescription.tsx +++ b/apps/web/src/components/roadmap/MarkdownDescription.tsx @@ -19,29 +19,32 @@ function safeMarkdownUrl(url: string): string | undefined { export function MarkdownDescription({ value }: MarkdownDescriptionProps) { return ( -
- { - if (!href) return <>{children} +
+

Description:

+
+ { + if (!href) return <>{children} - const external = /^https?:/i.test(href) - return ( - - {children} - - ) - }, - }} - > - {value} - -
+ const external = /^https?:/i.test(href) + return ( + + {children} + + ) + }, + }} + > + {value} + +
+ ) } From 83d2a88de00558baa5c787ae08174c5da7f051f9 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:26:09 +0100 Subject: [PATCH 05/13] style(web): clarify expanded task descriptions --- apps/web/src/styles/workspace/task-description.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/web/src/styles/workspace/task-description.css b/apps/web/src/styles/workspace/task-description.css index 1daa01f..40ba6c2 100644 --- a/apps/web/src/styles/workspace/task-description.css +++ b/apps/web/src/styles/workspace/task-description.css @@ -1,4 +1,18 @@ /* ─── Task description display ─────────────────────────────────────────────── */ +.task-description-block { + min-width: 0; +} +.task-description-label { + display: flex; + align-items: center; + min-height: 30px; + margin: 0 0 4px; + padding-right: 118px; + color: var(--ink-1); + font-size: 14px; + letter-spacing: 0; + text-transform: none; +} .task-detail .desc { font-size: 14.5px; color: var(--ink-2); From 27af21546661b42f43ff3725e43a244af0469b3d Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:26:40 +0100 Subject: [PATCH 06/13] style(web): align task links and actions --- apps/web/src/styles/workspace/task-detail.css | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/web/src/styles/workspace/task-detail.css b/apps/web/src/styles/workspace/task-detail.css index 3ecf7c7..a70149b 100644 --- a/apps/web/src/styles/workspace/task-detail.css +++ b/apps/web/src/styles/workspace/task-detail.css @@ -1,11 +1,29 @@ /* ─── Task detail panel ────────────────────────────────────────────────────── */ .task-detail { padding: 2px 22px 18px 56px; - display: flex; flex-direction: column; - gap: 10px; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px 16px; animation: detailIn 240ms cubic-bezier(.2,.8,.2,1); position: relative; } +.task-detail > * { + grid-column: 1 / -1; + min-width: 0; +} +.task-detail > .task-links { + grid-column: 1; + align-self: start; +} +.task-detail > .task-action-area:not(.has-form) { + grid-column: 2; + align-self: start; + justify-self: end; + margin-top: 4px; +} +.task-detail > .task-action-area.has-form { + grid-column: 1 / -1; +} /* ─── Task detail sections ─────────────────────────────────────────────────── */ .task-detail-section { margin-top: 4px; } @@ -119,7 +137,15 @@ /* ── 600px — mobile: compact task detail density ────────────────────────── */ @media (max-width: 600px) { - .task-detail { gap: 8px; } + .task-detail { + grid-template-columns: minmax(0, 1fr); + gap: 8px; + } + .task-detail > .task-links, + .task-detail > .task-action-area:not(.has-form) { + grid-column: 1; + justify-self: stretch; + } .task-link-form { grid-template-columns: 1fr; } .task-link-form-actions { justify-content: flex-end; } } From 831826f5f998cea015936c190ed070dcc193d044 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:27:05 +0100 Subject: [PATCH 07/13] style(web): position expanded task actions --- apps/web/src/styles/workspace/task-actions.css | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/web/src/styles/workspace/task-actions.css b/apps/web/src/styles/workspace/task-actions.css index 8ff3384..f712b29 100644 --- a/apps/web/src/styles/workspace/task-actions.css +++ b/apps/web/src/styles/workspace/task-actions.css @@ -18,11 +18,12 @@ max-width: min(100%, 720px); display: flex; align-items: center; - justify-content: flex-start; + justify-content: flex-end; flex-wrap: wrap; } .task-action-area.has-form { width: min(100%, 720px); + justify-self: start; } .task-detail .actions { @@ -30,7 +31,7 @@ gap: 6px; flex-wrap: wrap; align-items: center; - justify-content: flex-start; + justify-content: flex-end; } .task-detail .actions .btn { height: 30px; @@ -40,6 +41,10 @@ gap: 6px; } .task-detail .actions .task-details-action { + position: absolute; + top: 2px; + right: 22px; + z-index: 1; color: var(--ink-2); border-color: var(--border-faint); background: var(--bg-2); @@ -105,11 +110,16 @@ /* ─── Responsive ───────────────────────────────────────────────────────────── */ +/* ── 760px — keep the edit action aligned with compact detail padding ─── */ +@media (max-width: 760px) { + .task-detail .actions .task-details-action { right: 16px; } +} + /* ── 600px — mobile: full-width task actions ───────────────────────────── */ @media (max-width: 600px) { .task-action-area, .task-detail .actions { width: 100%; } - .task-detail .actions .btn { + .task-detail .actions .btn:not(.task-details-action) { flex: 1 1 140px; justify-content: center; } From 62c2ee768ae7315864a1c1c322c17a74efd0a65c Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:27:50 +0100 Subject: [PATCH 08/13] style(web): add task tag preference controls --- .../src/styles/workspace/task-metadata.css | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/apps/web/src/styles/workspace/task-metadata.css b/apps/web/src/styles/workspace/task-metadata.css index 9bea71f..09c4419 100644 --- a/apps/web/src/styles/workspace/task-metadata.css +++ b/apps/web/src/styles/workspace/task-metadata.css @@ -71,6 +71,58 @@ text-overflow: ellipsis; white-space: nowrap; } +.task-row-tag.is-favorite { + box-shadow: 0 0 0 1px color-mix(in srgb, var(--tag-color, var(--ink-4)), transparent 72%); +} +.task-tag-preferences { + gap: 6px; +} +.automatic-tag-toggle, +.favorite-tag-option { + display: inline-flex; + align-items: center; + gap: 4px; + min-height: 22px; + border-radius: 5px; + cursor: pointer; +} +.automatic-tag-toggle { + padding: 2px 7px; + border: 1px dashed var(--border); + background: var(--bg-inset); + color: var(--ink-4); + font-size: 11px; + font-weight: 600; +} +.automatic-tag-toggle.selected { + border-style: solid; + border-color: color-mix(in srgb, var(--sage), transparent 55%); + background: color-mix(in srgb, var(--sage), transparent 88%); + color: var(--sage); +} +.favorite-tag-option { + padding: 1px; + border: 1px solid transparent; + background: transparent; + color: var(--ink-4); +} +.favorite-tag-option.selected { + border-color: color-mix(in srgb, var(--ember), transparent 55%); + background: var(--lava-glow); + color: var(--ember); +} +.favorite-tag-option .tag-chip { + pointer-events: none; +} +.automatic-tag-toggle:focus-visible, +.favorite-tag-option:focus-visible { + outline: 2px solid var(--ember); + outline-offset: 2px; +} +.automatic-tag-toggle:hover, +.favorite-tag-option:hover { + border-color: var(--border); +} .estimate-chip { display: inline-flex; align-items: center; From 78b796e0c4f5e6a1bf4000d1d409243b868dfd2a Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:28:13 +0100 Subject: [PATCH 09/13] test(web): cover task tag display filtering --- .../useTaskTagDisplayPreferences.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 apps/web/src/hooks/__tests__/useTaskTagDisplayPreferences.test.ts diff --git a/apps/web/src/hooks/__tests__/useTaskTagDisplayPreferences.test.ts b/apps/web/src/hooks/__tests__/useTaskTagDisplayPreferences.test.ts new file mode 100644 index 0000000..4c1db91 --- /dev/null +++ b/apps/web/src/hooks/__tests__/useTaskTagDisplayPreferences.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' +import { + getUserDisplayTags, + isAutomaticTaskTag, +} from '../useTaskTagDisplayPreferences' + +describe('task tag display preferences', () => { + it('recognizes status namespace tags as automatic', () => { + expect(isAutomaticTaskTag('status:done')).toBe(true) + expect(isAutomaticTaskTag(' STATUS:in-progress ')).toBe(true) + expect(isAutomaticTaskTag('priority:P0')).toBe(false) + }) + + it('keeps custom tags in their authored order', () => { + expect(getUserDisplayTags([ + 'status:done', + 'priority:P0', + 'area:coordination', + 'status:planned', + ])).toEqual([ + 'priority:P0', + 'area:coordination', + ]) + }) +}) From 4957f7b78ae2043a1b89af9e8164bb75ac45f4ab Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:28:43 +0100 Subject: [PATCH 10/13] test(web): cover expanded description label --- .../roadmap/__tests__/MarkdownDescription.test.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/web/src/components/roadmap/__tests__/MarkdownDescription.test.tsx b/apps/web/src/components/roadmap/__tests__/MarkdownDescription.test.tsx index 2d4b241..e4f65b8 100644 --- a/apps/web/src/components/roadmap/__tests__/MarkdownDescription.test.tsx +++ b/apps/web/src/components/roadmap/__tests__/MarkdownDescription.test.tsx @@ -25,6 +25,13 @@ afterEach(() => { }) describe('MarkdownDescription', () => { + it('labels the expanded task description', () => { + render('Task details') + + expect(container.querySelector('.task-description-label')?.textContent).toBe('Description:') + expect(container.querySelector('.markdown-description')?.textContent).toBe('Task details') + }) + it('renders the roadmap fixture headings, lists, and bold labels', () => { render(`### Objective From 60e3b3545c385c29c14bf06bef6176ad6f22f2ba Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:32:48 +0100 Subject: [PATCH 11/13] fix(web): decouple task tag preferences from context --- .../src/hooks/useTaskTagDisplayPreferences.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/web/src/hooks/useTaskTagDisplayPreferences.ts b/apps/web/src/hooks/useTaskTagDisplayPreferences.ts index 417ce3f..7d6738c 100644 --- a/apps/web/src/hooks/useTaskTagDisplayPreferences.ts +++ b/apps/web/src/hooks/useTaskTagDisplayPreferences.ts @@ -1,7 +1,7 @@ 'use client' import { useCallback, useEffect, useMemo, useState } from 'react' -import { useRoadmapLifecycle } from '@/context/RoadmapContext' +import { storage } from '@/lib/storage' interface TaskTagDisplayPreferences { favoriteTagId: string | null @@ -47,8 +47,20 @@ function readPreferences(storageKey: string): TaskTagDisplayPreferences { } } +function writePreferences( + storageKey: string, + preferences: TaskTagDisplayPreferences, +): void { + if (typeof window === 'undefined') return + try { + window.localStorage.setItem(storageKey, JSON.stringify(preferences)) + } catch { + // The preference remains active for this session when browser storage is blocked. + } +} + export function useTaskTagDisplayPreferences(taskId: string, tagIds: string[]) { - const { activeRoadmapId } = useRoadmapLifecycle() + const activeRoadmapId = storage.getActiveRoadmapId() const userTags = useMemo(() => getUserDisplayTags(tagIds), [tagIds]) const storageKey = `${STORAGE_PREFIX}:${activeRoadmapId ?? 'local'}:${taskId}` const [preferences, setPreferences] = useState( @@ -83,10 +95,12 @@ export function useTaskTagDisplayPreferences(taskId: string, tagIds: string[]) { ) => { setPreferences((current) => { const next = updater(current) - window.localStorage.setItem(storageKey, JSON.stringify(next)) - window.dispatchEvent(new CustomEvent(PREFERENCE_EVENT, { - detail: { storageKey, preferences: next }, - })) + writePreferences(storageKey, next) + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent(PREFERENCE_EVENT, { + detail: { storageKey, preferences: next }, + })) + } return next }) }, [storageKey]) From 537cfd46b2580315961beafeee04f60219d93097 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:48:08 +0100 Subject: [PATCH 12/13] fix(web): keep task detail flow stable for drag --- apps/web/src/styles/workspace/task-detail.css | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/apps/web/src/styles/workspace/task-detail.css b/apps/web/src/styles/workspace/task-detail.css index a70149b..f6d5924 100644 --- a/apps/web/src/styles/workspace/task-detail.css +++ b/apps/web/src/styles/workspace/task-detail.css @@ -1,28 +1,28 @@ /* ─── Task detail panel ────────────────────────────────────────────────────── */ .task-detail { padding: 2px 22px 18px 56px; - display: grid; - grid-template-columns: minmax(0, 1fr) auto; + display: flex; + flex-flow: row wrap; gap: 10px 16px; animation: detailIn 240ms cubic-bezier(.2,.8,.2,1); position: relative; } .task-detail > * { - grid-column: 1 / -1; + flex: 0 0 100%; min-width: 0; } .task-detail > .task-links { - grid-column: 1; - align-self: start; + flex: 1 1 320px; + align-self: flex-start; } .task-detail > .task-action-area:not(.has-form) { - grid-column: 2; - align-self: start; - justify-self: end; + flex: 0 0 auto; + align-self: flex-start; margin-top: 4px; + margin-left: auto; } .task-detail > .task-action-area.has-form { - grid-column: 1 / -1; + flex-basis: 100%; } /* ─── Task detail sections ─────────────────────────────────────────────────── */ @@ -137,14 +137,11 @@ /* ── 600px — mobile: compact task detail density ────────────────────────── */ @media (max-width: 600px) { - .task-detail { - grid-template-columns: minmax(0, 1fr); - gap: 8px; - } + .task-detail { gap: 8px; } .task-detail > .task-links, .task-detail > .task-action-area:not(.has-form) { - grid-column: 1; - justify-self: stretch; + flex-basis: 100%; + margin-left: 0; } .task-link-form { grid-template-columns: 1fr; } .task-link-form-actions { justify-content: flex-end; } From 4a68219561b608befbbfaf91177dce4a54b51e36 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:04:30 +0100 Subject: [PATCH 13/13] test(web): assert persisted pointer reorder direction --- apps/web/e2e/accessibility.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/web/e2e/accessibility.spec.ts b/apps/web/e2e/accessibility.spec.ts index 639dcdf..324dac0 100644 --- a/apps/web/e2e/accessibility.spec.ts +++ b/apps/web/e2e/accessibility.spec.ts @@ -423,6 +423,10 @@ test('reorders tasks and subtasks by drag only and persists local order', async const secondSubtask = reorderedTask.locator('.subtask-row').filter({ hasText: 'Second subtask', }) + await expect(reorderedTask.locator('.subtask-title')).toHaveText([ + 'Second subtask', + 'First subtask', + ]) await dragHandleOnto( page, secondSubtask.locator('.subtask-drag-handle'), @@ -430,8 +434,8 @@ test('reorders tasks and subtasks by drag only and persists local order', async ) await expect(reorderedTask.locator('.subtask-title')).toHaveText([ - 'Second subtask', 'First subtask', + 'Second subtask', ]) await expect(reorderedTask.locator('.subtask-row .task-num')).toHaveText([ '1.1.1', @@ -444,8 +448,8 @@ test('reorders tasks and subtasks by drag only and persists local order', async await persistedTask.getByRole('button', { name: 'Expand task' }).click() } await expect(persistedTask.locator('.subtask-title')).toHaveText([ - 'Second subtask', 'First subtask', + 'Second subtask', ]) await expect(persistedTask.locator('.subtask-row .task-num')).toHaveText([ '1.1.1',