From 9a40d7df91746630dfd049c928b3cdd37cc03275 Mon Sep 17 00:00:00 2001 From: AiraAgent <305439133+AiraAgent@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:15:34 +1100 Subject: [PATCH 1/3] feat(appearance): add compact task cards --- .../src/composables/useBoardCardGeometry.ts | 24 ++++ apps/desktop/src/renderer/src/constants/ui.ts | 3 + .../{fragments}/TaskCard/TaskCard.vue | 83 +++++++++++-- .../GeneralSettings/GeneralSettings.vue | 13 ++- .../modules/TaskBoard/TaskCard.test.ts | 110 +++++++++++++++++- .../renderer/stores/settings.store.test.ts | 32 +++++ .../views/Settings/GeneralSettings.test.ts | 18 ++- .../core/src/storage/models/SettingsModel.ts | 12 +- .../core/src/storage/models/_rowMappers.ts | 11 +- .../storage/models/SettingsModel.test.ts | 57 +++++++++ packages/protocol/src/types/storage.ts | 2 + 11 files changed, 344 insertions(+), 21 deletions(-) create mode 100644 apps/desktop/src/renderer/src/composables/useBoardCardGeometry.ts diff --git a/apps/desktop/src/renderer/src/composables/useBoardCardGeometry.ts b/apps/desktop/src/renderer/src/composables/useBoardCardGeometry.ts new file mode 100644 index 00000000..3d71415b --- /dev/null +++ b/apps/desktop/src/renderer/src/composables/useBoardCardGeometry.ts @@ -0,0 +1,24 @@ +import {computed} from "vue" + +import {BOARD_CARD_GAP, BOARD_CARD_HEIGHT, COMPACT_BOARD_CARD_HEIGHT} from "@/constants/ui" +import {useSettingValue} from "./useSettingValue" + +import type {FontSize, TaskView} from "@daily/protocol" + +/** Reactive card dimensions shared by presentation, virtual columns, navigation and drag placement. */ +export function useBoardCardGeometry() { + const storedTaskView = useSettingValue<"appearance.taskView", TaskView>("appearance.taskView", "regular") + const fontSize = useSettingValue<"typography.fontSize", FontSize>("typography.fontSize", "normal") + const fontSizePx: Record = {small: 13, normal: 15, large: 17} + const taskView = computed({ + get: () => (storedTaskView.value === "compact" ? "compact" : "regular"), + set: (value) => { + storedTaskView.value = value === "compact" ? "compact" : "regular" + }, + }) + const isCompact = computed(() => taskView.value === "compact") + const cardHeight = computed(() => (isCompact.value ? (COMPACT_BOARD_CARD_HEIGHT * (fontSizePx[fontSize.value] ?? 15)) / 15 : BOARD_CARD_HEIGHT)) + const cardStep = computed(() => cardHeight.value + BOARD_CARD_GAP) + + return {taskView, isCompact, cardHeight, cardStep} +} diff --git a/apps/desktop/src/renderer/src/constants/ui.ts b/apps/desktop/src/renderer/src/constants/ui.ts index 0aeddf50..111f6293 100644 --- a/apps/desktop/src/renderer/src/constants/ui.ts +++ b/apps/desktop/src/renderer/src/constants/ui.ts @@ -44,3 +44,6 @@ export const BOARD_CARD_HEIGHT = 200 /** A board card plus the gap below it; the virtual columns and the drop index step through a column by it, so it must match the card. */ export const BOARD_CARD_STEP = 206 + +export const BOARD_CARD_GAP = BOARD_CARD_STEP - BOARD_CARD_HEIGHT +export const COMPACT_BOARD_CARD_HEIGHT = 108 diff --git a/apps/desktop/src/renderer/src/ui/modules/TaskBoard/{fragments}/TaskCard/TaskCard.vue b/apps/desktop/src/renderer/src/ui/modules/TaskBoard/{fragments}/TaskCard/TaskCard.vue index 297b5442..4a3dc32a 100644 --- a/apps/desktop/src/renderer/src/ui/modules/TaskBoard/{fragments}/TaskCard/TaskCard.vue +++ b/apps/desktop/src/renderer/src/ui/modules/TaskBoard/{fragments}/TaskCard/TaskCard.vue @@ -4,7 +4,7 @@ import {computed, toRef, useTemplateRef} from "vue" import {sortTags} from "@daily/protocol" import {toDateLabel} from "@daily/std" -import {BOARD_CARD_HEIGHT} from "@/constants/ui" +import {useBoardCardGeometry} from "@/composables/useBoardCardGeometry" import {useBranchesStore} from "@/stores/branches.store" import {useFilterStore} from "@/stores/filter.store" import {useFocusStore} from "@/stores/focus.store" @@ -29,6 +29,7 @@ import PriorityIcon from "@/ui/common/priority/PriorityIcon.vue" import {useConfirmUnsavedModal} from "@/ui/overlays/ConfirmUnsavedModal" import {toShortDurationLabel} from "@/utils/date/toShortDurationLabel" import {cn} from "@/utils/ui/tailwindcss" +import {toTaskTitle} from "@shared/utils/tasks/toTaskTitle" import CardCrumb from "./{fragments}/CardCrumb.vue" import DeleteMenuItem from "./{fragments}/DeleteMenuItem.vue" import FocusBorder from "./{fragments}/FocusBorder.vue" @@ -41,6 +42,9 @@ import type {Branch, Tag, Task, TaskRelationSets, TaskStatus} from "@daily/proto const props = defineProps<{task: Task}>() +const {isCompact, cardHeight} = useBoardCardGeometry() +const title = computed(() => toTaskTitle(props.task.content) || "Untitled task") + const tasksStore = useTasksStore() const tagsStore = useTagsStore() const taskEditorStore = useTaskEditorStore() @@ -70,6 +74,7 @@ const footerDayLabel = computed(() => { }) const hasTime = computed(() => Boolean(estimateLabel.value) || Boolean(spentLabel.value)) +const hasContext = computed(() => hasRelation.value || Boolean(footerDayLabel.value) || commentCount.value > 0) const hasMetrics = computed(() => hasRelation.value || Boolean(footerDayLabel.value) || hasTime.value || commentCount.value > 0) const hasPriority = computed(() => props.task.priority !== "none") const hasFooter = computed(() => tags.value.length > 0 || hasMetrics.value || hasPriority.value) @@ -164,7 +169,7 @@ function getStatusClass(status: TaskStatus) { function getCardClasses(status: TaskStatus, isInSession: boolean) { return cn( - "bg-base-100 hover:shadow-accent/5 group relative overflow-hidden rounded-2xl border transition-all duration-200 hover:shadow-lg", + "bg-base-100 hover:shadow-accent/5 group relative overflow-hidden rounded-2xl border transition-[border-color,box-shadow] duration-200 hover:shadow-lg", status === "backlog" && "border-base-content/15 border-dashed", status === "done" && "border-success/30 hover:border-success/40", status === "discarded" && "border-warning/30 hover:border-warning/40", @@ -183,7 +188,8 @@ function getSpentClasses(hasSpent: boolean) { function getContentClasses(status: TaskStatus) { return cn( - "board-card-text relative min-h-0 flex-1 overflow-hidden transition-opacity duration-200", + "relative min-h-0 flex-1 overflow-hidden transition-opacity duration-200", + !isCompact.value && "board-card-text", (status === "done" || status === "discarded") && "opacity-50", ) } @@ -218,20 +224,29 @@ async function onLinkTask(side: keyof TaskRelationSets, taskId: Task["id"]) {