feat: show time-remaining countdown on dated upcoming tasks #238

Merged
Copilot merged 4 commits from copilot/add-countdown-to-task-dates into main 2026-05-01 18:05:01 +00:00
Copilot commented 2026-04-30 20:11:57 +00:00 (Migrated from github.com)

Dated tasks in the Dashboard "Tasks & Chores" upcoming section now display a compact time-until string alongside the date, e.g. (May 22, 4m2w1d).

Description

Adds a formatTimeUntil composable function and wires it into the task chip date display. Only future dates get the countdown; today/overdue silently omit it.

useDate.ts — new formatTimeUntil(dateString):

  • Accepts string | Date | null | undefined; returns '' for falsy, past, or same-day inputs
  • Applies timezone-offset correction only for UTC string inputs (bare date strings or explicit UTC/offset strings); Date instances are used as-is
  • Uses DST-safe UTC day arithmetic (Date.UTC) to avoid ±1 day errors around daylight-saving transitions
  • Decomposes remaining days into months (÷30), weeks (÷7), days; returns only non-zero components joined: 4m2w1d, 2w3d, 5d, etc.

TaskChip.vue — compact chip and card views both updated:

  • A taskTimeUntil computed property evaluates formatTimeUntil(props.task.due_date) once per render, avoiding duplicate parsing in both the chip and card view template locations
  • Both views render the countdown inline with the date:
({{ formatDate(task.due_date, true) }}{{ taskTimeUntil ? ', ' + taskTimeUntil : '' }})
<!-- renders as: (May 22, 4m2w1d) -->

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (code change that neither fixes a bug nor adds a feature)
  • Performance improvement
  • Test coverage improvement

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this manually (if applicable)

Code Quality

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors

CI/CD Checks

  • All automated tests pass
  • Code formatting (Pint) passes
  • JavaScript/TypeScript linting passes
  • Build process completes successfully

Additional Notes

Month approximation uses a fixed 30-day bucket (consistent with a compact display — not calendar-accurate). If calendar-accurate month boundaries are needed in future, formatTimeUntil is self-contained and easy to swap.

Dated tasks in the Dashboard "Tasks &amp; Chores" upcoming section now display a compact time-until string alongside the date, e.g. `(May 22, 4m2w1d)`. ## Description Adds a `formatTimeUntil` composable function and wires it into the task chip date display. Only future dates get the countdown; today/overdue silently omit it. **`useDate.ts`** — new `formatTimeUntil(dateString)`: - Accepts `string | Date | null | undefined`; returns `''` for falsy, past, or same-day inputs - Applies timezone-offset correction only for UTC string inputs (bare date strings or explicit UTC/offset strings); `Date` instances are used as-is - Uses DST-safe UTC day arithmetic (`Date.UTC`) to avoid ±1 day errors around daylight-saving transitions - Decomposes remaining days into months (÷30), weeks (÷7), days; returns only non-zero components joined: `4m2w1d`, `2w3d`, `5d`, etc. **`TaskChip.vue`** — compact chip and card views both updated: - A `taskTimeUntil` computed property evaluates `formatTimeUntil(props.task.due_date)` once per render, avoiding duplicate parsing in both the chip and card view template locations - Both views render the countdown inline with the date: ```html ({{ formatDate(task.due_date, true) }}{{ taskTimeUntil ? ', ' + taskTimeUntil : '' }}) <!-- renders as: (May 22, 4m2w1d) --> ``` ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [x] Refactoring (code change that neither fixes a bug nor adds a feature) - [ ] Performance improvement - [ ] Test coverage improvement ## Testing - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [x] I have tested this manually (if applicable) ## Code Quality - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [x] My changes generate no new warnings or errors ## CI/CD Checks - [ ] All automated tests pass - [ ] Code formatting (Pint) passes - [ ] JavaScript/TypeScript linting passes - [ ] Build process completes successfully ## Additional Notes Month approximation uses a fixed 30-day bucket (consistent with a compact display — not calendar-accurate). If calendar-accurate month boundaries are needed in future, `formatTimeUntil` is self-contained and easy to swap.
coderabbitai[bot] commented 2026-04-30 20:12:04 +00:00 (Migrated from github.com)

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 4a600f84-d026-4c96-bdfc-43ee91dd5e14

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- This is an auto-generated comment: skip review by coderabbit.ai --> > [!IMPORTANT] > ## Review skipped > > Bot user detected. > > To trigger a single review, invoke the `@coderabbitai review` command. > > <details> > <summary>⚙️ Run configuration</summary> > > **Configuration used**: defaults > > **Review profile**: CHILL > > **Plan**: Free > > **Run ID**: `4a600f84-d026-4c96-bdfc-43ee91dd5e14` > > </details> > > You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file. > > Use the checkbox below for a quick retry: > - [ ] <!-- {"checkboxId": "e9bb8d72-00e8-4f67-9cb2-caf3b22574fe"} --> 🔍 Trigger review <!-- end of auto-generated comment: skip review by coderabbit.ai --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end -->
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-04-30 20:18:50 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a compact “time remaining” countdown to upcoming dated tasks in the Dashboard by introducing a shared date utility and wiring it into the task date label.

Changes:

  • Added formatTimeUntil() to the useDate composable to compute a compact XmYwZd string for future dates.
  • Updated TaskChip.vue to append the time-until string alongside formatted due dates in both chip and card views.
  • Documented the feature in DEVLOG.md and CHANGELOG.md.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
resources/js/Composables/useDate.ts Adds formatTimeUntil() used to compute the compact countdown string.
resources/js/Components/Calendar/TaskChip.vue Appends the countdown string to the due-date display in both render variants.
DEVLOG.md Records the new feature and references the new composable function.
CHANGELOG.md Announces the improvement in the user-facing changelog.
## Pull request overview > [!NOTE] > Copilot was unable to run its full agentic suite in this review. Adds a compact “time remaining” countdown to upcoming dated tasks in the Dashboard by introducing a shared date utility and wiring it into the task date label. **Changes:** - Added `formatTimeUntil()` to the `useDate` composable to compute a compact `XmYwZd` string for future dates. - Updated `TaskChip.vue` to append the time-until string alongside formatted due dates in both chip and card views. - Documented the feature in `DEVLOG.md` and `CHANGELOG.md`. ### Reviewed changes Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments. | File | Description | | ---- | ----------- | | resources/js/Composables/useDate.ts | Adds `formatTimeUntil()` used to compute the compact countdown string. | | resources/js/Components/Calendar/TaskChip.vue | Appends the countdown string to the due-date display in both render variants. | | DEVLOG.md | Records the new feature and references the new composable function. | | CHANGELOG.md | Announces the improvement in the user-facing changelog. |
@ -8,7 +8,12 @@ Use `[1.CURRENT]` as the version heading for unreleased changes. This section mu
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-30 20:18:49 +00:00

The DEVLOG describes the output as \"Xm Yw Zd\" (with spaces), but the implementation returns a concatenated string without spaces (e.g., 4m2w1d). Please update the description to match the actual output format to avoid confusion for future readers.

- Added `formatTimeUntil(dateString)` to `useDate.ts` composable — returns a compact concatenated "XmYwZd" string (e.g. `4m2w1d`) representing the number of months, weeks, and days until the given date; returns an empty string for past or same-day dates
The DEVLOG describes the output as `\"Xm Yw Zd\"` (with spaces), but the implementation returns a concatenated string without spaces (e.g., `4m2w1d`). Please update the description to match the actual output format to avoid confusion for future readers. ```suggestion - Added `formatTimeUntil(dateString)` to `useDate.ts` composable — returns a compact concatenated "XmYwZd" string (e.g. `4m2w1d`) representing the number of months, weeks, and days until the given date; returns an empty string for past or same-day dates ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-30 20:18:49 +00:00

The template calls formatTimeUntil(task.due_date) twice in each location, which repeats date parsing/computation and makes the expression hard to read. Compute the value once (e.g., a computed property or a small helper in <script setup>) and render it with a simple conditional, improving readability and avoiding duplicated work.

The template calls `formatTimeUntil(task.due_date)` twice in each location, which repeats date parsing/computation and makes the expression hard to read. Compute the value once (e.g., a computed property or a small helper in `<script setup>`) and render it with a simple conditional, improving readability and avoiding duplicated work.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-30 20:18:49 +00:00

The template calls formatTimeUntil(task.due_date) twice in each location, which repeats date parsing/computation and makes the expression hard to read. Compute the value once (e.g., a computed property or a small helper in <script setup>) and render it with a simple conditional, improving readability and avoiding duplicated work.

The template calls `formatTimeUntil(task.due_date)` twice in each location, which repeats date parsing/computation and makes the expression hard to read. Compute the value once (e.g., a computed property or a small helper in `<script setup>`) and render it with a simple conditional, improving readability and avoiding duplicated work.
@ -47,7 +47,63 @@ export function useDate() {
return `${year}-${month}-${day}`;
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-30 20:18:48 +00:00

formatTimeUntil accepts string | Date, but it always applies a timezone-offset correction. If a Date instance is already in local time (common when dates are constructed in-app), adding the timezone offset will shift the day and can produce an incorrect countdown. Consider handling Date inputs without applying the offset correction (clone + normalize to midnight), and only apply the offset correction for string inputs that parse as UTC dates. Also, the if (!dateString) guard suggests the function expects nullable inputs—either widen the parameter type to include null | undefined or remove the guard to keep the API contract consistent.

    function formatTimeUntil(
        dateString: string | Date | null | undefined,
    ): string {
        if (!dateString) return '';

        const today = new Date();
        today.setHours(0, 0, 0, 0);

        let correctedDate: Date;

        if (dateString instanceof Date) {
            correctedDate = new Date(dateString.getTime());
        } else {
            const date = new Date(dateString);
            const shouldApplyTimezoneCorrection =
                /^\d{4}-\d{2}-\d{2}$/.test(dateString) ||
                /(?:Z|[+-]\d{2}:\d{2})$/.test(dateString);

            if (shouldApplyTimezoneCorrection) {
                const userTimezoneOffset = date.getTimezoneOffset() * 60000;
                correctedDate = new Date(date.getTime() + userTimezoneOffset);
            } else {
                correctedDate = date;
            }
        }
`formatTimeUntil` accepts `string | Date`, but it always applies a timezone-offset correction. If a `Date` instance is already in local time (common when dates are constructed in-app), adding the timezone offset will shift the day and can produce an incorrect countdown. Consider handling `Date` inputs without applying the offset correction (clone + normalize to midnight), and only apply the offset correction for string inputs that parse as UTC dates. Also, the `if (!dateString)` guard suggests the function expects nullable inputs—either widen the parameter type to include `null | undefined` or remove the guard to keep the API contract consistent. ```suggestion function formatTimeUntil( dateString: string | Date | null | undefined, ): string { if (!dateString) return ''; const today = new Date(); today.setHours(0, 0, 0, 0); let correctedDate: Date; if (dateString instanceof Date) { correctedDate = new Date(dateString.getTime()); } else { const date = new Date(dateString); const shouldApplyTimezoneCorrection = /^\d{4}-\d{2}-\d{2}$/.test(dateString) || /(?:Z|[+-]\d{2}:\d{2})$/.test(dateString); if (shouldApplyTimezoneCorrection) { const userTimezoneOffset = date.getTimezoneOffset() * 60000; correctedDate = new Date(date.getTime() + userTimezoneOffset); } else { correctedDate = date; } } ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-04-30 20:18:48 +00:00

Computing totalDays via (msDiff / 86400000) and Math.round can be off by ±1 around DST transitions because a “day” may be 23 or 25 hours in local time. Since you’re working with date-only semantics, consider using a DST-safe approach (e.g., comparing UTC date components / Date.UTC(y,m,d) values) or use Math.ceil for positive diffs after normalizing to midnight to avoid rounding down unexpectedly.

        const todayUtc = Date.UTC(
            today.getFullYear(),
            today.getMonth(),
            today.getDate(),
        );
        const correctedDateUtc = Date.UTC(
            correctedDate.getFullYear(),
            correctedDate.getMonth(),
            correctedDate.getDate(),
        );
        const totalDays =
            (correctedDateUtc - todayUtc) / (1000 * 60 * 60 * 24);
Computing `totalDays` via `(msDiff / 86400000)` and `Math.round` can be off by ±1 around DST transitions because a “day” may be 23 or 25 hours in local time. Since you’re working with date-only semantics, consider using a DST-safe approach (e.g., comparing UTC date components / `Date.UTC(y,m,d)` values) or use `Math.ceil` for positive diffs after normalizing to midnight to avoid rounding down unexpectedly. ```suggestion const todayUtc = Date.UTC( today.getFullYear(), today.getMonth(), today.getDate(), ); const correctedDateUtc = Date.UTC( correctedDate.getFullYear(), correctedDate.getMonth(), correctedDate.getDate(), ); const totalDays = (correctedDateUtc - todayUtc) / (1000 * 60 * 60 * 24); ```
niddelicious (Migrated from github.com) reviewed 2026-05-01 16:34:33 +00:00
niddelicious (Migrated from github.com) commented 2026-05-01 16:34:33 +00:00

@copilot apply changes based on this feedback

@copilot apply changes based on [this feedback](https://github.com/niddelicious/parenting-tool/pull/238#discussion_r3170632226)
copilot-swe-agent[bot] (Migrated from github.com) reviewed 2026-05-01 16:39:08 +00:00
copilot-swe-agent[bot] (Migrated from github.com) commented 2026-05-01 16:39:08 +00:00

Done in commit 1443c3d. Added a taskTimeUntil computed property that evaluates formatTimeUntil(props.task.due_date) once, and both template locations (chip and card view) now reference taskTimeUntil directly.

Done in commit `1443c3d`. Added a `taskTimeUntil` computed property that evaluates `formatTimeUntil(props.task.due_date)` once, and both template locations (chip and card view) now reference `taskTimeUntil` directly.
niddelicious (Migrated from github.com) reviewed 2026-05-01 17:51:26 +00:00
niddelicious (Migrated from github.com) commented 2026-05-01 17:51:26 +00:00

@copilot apply changes based on this feedback

@copilot apply changes based on [this feedback](https://github.com/niddelicious/parenting-tool/pull/238#discussion_r3170632242)
copilot-swe-agent[bot] (Migrated from github.com) reviewed 2026-05-01 17:54:27 +00:00
copilot-swe-agent[bot] (Migrated from github.com) commented 2026-05-01 17:54:27 +00:00

This was already handled in commit 1443c3d — the taskTimeUntil computed property (line 86) covers both the chip view (line 109) and the card view (line 154).

This was already handled in commit `1443c3d` — the `taskTimeUntil` computed property (line 86) covers both the chip view (line 109) and the card view (line 154).
niddelicious (Migrated from github.com) approved these changes 2026-05-01 18:04:42 +00:00
niddelicious (Migrated from github.com) left a comment

🤷‍♂️

🤷‍♂️
Sign in to join this conversation.
No description provided.