feat(tradein-ui): mock-disclaimer + progressive enrichment + UX polish #517

Merged
lekss361 merged 2 commits from feat/tradein-ui-mock-disclaimer-enrichment into main 2026-05-24 12:58:39 +00:00
Owner

Changes

  • Plaque «адрес распознан неточно» в HeroSummary when target_address has Склад|Гараж|Подсобка|Нежилое, prefix or confidence_explanation starts with address_not_geocoded
  • Progressive enrichment — inline form house_type + repair_state if either is NULL AND confidence != low; POST /estimate re-run with merged patch
  • DealsCard — returns null if actual_deals.length === 0 (no empty section)
  • est_days_on_market — shows нет данных (greyed) when null

Files

  • tradein-mvp/frontend/src/app/page.tsx (resubmit handler)
  • tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx (3 changes)
  • tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx (empty-state hide)

Validation

Lint/tsc unable to run in isolated worktree (node_modules missing). Pre-commit hooks passed. Type contract follows existing EstimateResponse/HouseType/RepairState from lib/types.

Roadmap

PR 7 of 8 — [[Decision_TradeIn_DataQuality_8PR_Roadmap]].

Manual verify after deploy

  1. Open https://gendsgn.ru/trade-in?id=a0a0b820-e8a8-4eee-aa73-0ab3b98ac233 — should see plaque (target_address has Склад,).
  2. Open any estimate with house_type=null — enrichment form visible.
  3. Open estimate with empty deals — no empty card.
## Changes - **Plaque** «адрес распознан неточно» в `HeroSummary` when `target_address` has `Склад|Гараж|Подсобка|Нежилое,` prefix or `confidence_explanation` starts with `address_not_geocoded` - **Progressive enrichment** — inline form `house_type` + `repair_state` if either is NULL AND confidence != low; POST /estimate re-run with merged patch - **DealsCard** — returns `null` if `actual_deals.length === 0` (no empty section) - **est_days_on_market** — shows `нет данных` (greyed) when null ## Files - `tradein-mvp/frontend/src/app/page.tsx` (resubmit handler) - `tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx` (3 changes) - `tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx` (empty-state hide) ## Validation Lint/tsc unable to run in isolated worktree (node_modules missing). Pre-commit hooks passed. Type contract follows existing `EstimateResponse`/`HouseType`/`RepairState` from `lib/types`. ## Roadmap PR 7 of 8 — `[[Decision_TradeIn_DataQuality_8PR_Roadmap]]`. ## Manual verify after deploy 1. Open https://gendsgn.ru/trade-in?id=a0a0b820-e8a8-4eee-aa73-0ab3b98ac233 — should see plaque (target_address has `Склад,`). 2. Open any estimate with `house_type=null` — enrichment form visible. 3. Open estimate with empty deals — no empty card.
lekss361 added 1 commit 2026-05-24 12:24:48 +00:00
- Plaque «адрес распознан неточно» when target_address has 'Склад|Гараж|…'
  prefix or confidence_explanation starts with address_not_geocoded
- Inline form to fill house_type/repair_state if NULL → POST /estimate re-run
- Hide actual_deals section if empty
- 'нет данных' label for est_days_on_market when null

Source: estimate a0a0b820-e8a8-4eee-aa73-0ab3b98ac233 — UI fallouts from
backend roadmap (PR 7 of 8).
Author
Owner

Deep Code Review — verdict: REQUEST CHANGES

Summary

  • Status: 🟠 High (1) + 🟡 Medium / Low (3)
  • Files: 3 (P1: HeroSummary.tsx, P2: page.tsx + DealsCard.tsx)
  • Lines: +113 / -19 · PR: #517
  • Vault cross-check: matches Decision_TradeIn_DataQuality_8PR_Roadmap items #6 (mock disclaimer) + #11 (progressive enrichment). Scope correct.

🟠 High — Tailwind classes used but Tailwind NOT installed in tradein-mvp/frontend

File: tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx

Two new blocks use Tailwind utility classes:

  1. Mock disclaimer (lines ~104-109):
    className="rounded-lg border border-amber-500/40 bg-amber-50 px-4 py-3 text-sm text-amber-900"
    
  2. Enrichment form wrapper (line ~325):
    className="rounded-lg border border-slate-200 bg-slate-50 p-4 space-y-3"
    
    plus text-sm font-medium, control (custom), btn btn-primary (custom — these work).

Problem: tradein-mvp/frontend/package.json has NO tailwindcss dependency. There is no tailwind.config.{js,ts}, no postcss.config.*, no @tailwind directive in globals.css or trade-in.css. The whole subproject uses semantic class names (card, card-head, meta-row, hero-card, …) backed by trade-in.css with OKLCH design tokens.

Effect: all Tailwind classes are inert. The mock disclaimer (THE headline feature of this PR — per roadmap #6) will render as plain unstyled text on the card surface — no amber border, no warning-background, no padding (only margin: 16px 16px 0 inline applies). That's a functional UX regression of the feature itself: a non-styled warning is no warning.

Fix options:

Option A — inline styles (minimal diff, matches existing patterns in this file):

{showMockDisclaimer && (
  <div
    role="status"
    aria-live="polite"
    style={{
      margin: "16px 16px 0",
      padding: "12px 16px",
      border: "1px solid var(--accent-2)",
      background: "var(--accent-2-soft)",
      color: "var(--fg)",
      borderRadius: "var(--radius)",
      fontSize: 13,
    }}
  >
    Адрес распознан неточно  оценка может быть приближённой.
    Уточните адрес и пересчитайте для более точного результата.
  </div>
)}

Option B — add CSS classes to trade-in.css: .hero-disclaimer, .hero-enrich, .hero-enrich select, etc. — preferred for reuse and theme consistency. The file already defines --accent-2 / --accent-2-soft (orange/warn) which is the natural choice for this disclaimer.

Apply the same to the enrichment form block. Pick one approach but don't ship Tailwind classes here.


🟡 Medium — Mock disclaimer not announced by screen readers

File: HeroSummary.tsx ~104

The disclaimer appears conditionally after async data load. Without role="status" + aria-live="polite" (or role="alert" for stronger urgency), AT users won't be notified that the estimate is approximate. Add to the wrapper <div> (see snippet above).


🟢 Low — handleResubmit declared before resultData

File: tradein-mvp/frontend/src/app/page.tsx lines 62-65 reference resultData defined at line 69.

Runtime-safe (callback fires after render completes; TDZ won't trip), but ESLint no-use-before-define may flag this. PR description acknowledges lint never ran in the isolated worktree. Consider moving the handleResubmit declaration AFTER resultData, or convert resultData to a useMemo/useState hoisted above.


🟢 Low — Disclaimer wording diverges from roadmap

Roadmap (Decision_TradeIn_DataQuality_8PR_Roadmap row #6) specifies «Адрес не распознан — оценка примерная (mock-данные)». PR uses «Адрес распознан неточно — … приближённой». The PR phrasing is softer and arguably better UX, but it drops the explicit «mock-данные» marker. Worth a quick alignment ping with product. Not a blocker.


Cross-file impact (verified)

  • DealsCard.tsx: returning null when actual_deals.length === 0 — no caller iterates over the rendered output; page.tsx renders <DealsCard … /> inline, so a null render is harmless.
  • page.tsx handleResubmit: correctly merges resultData.input + patch and re-runs handleSubmit. URL replace + freshResult overwrite afterward — TanStack mutation queues on the same key; double-submit guarded by isResubmitting disabled state.
  • SourcesProgress consumes the old estimate during resubmit pending — no flash-of-mocked-content because resultData only switches in onSuccess.
  • Types HouseType / RepairState re-exported from @/types/trade-in — matches existing schema.
  • est_days_on_market field exists on AggregatedEstimate and may be null — handled.

Security / XSS (per spec focus)

  • Disclaimer text is hardcoded; no user content rendered as HTML.
  • Enrichment values come from controlled <select> whose <option value> exactly matches the HouseType / RepairState union — the as HouseType cast is safe.
  • No safeUrl usage needed — no URLs constructed from user input in this PR.
  • No new external resource loading.

Performance

  • No N+1, no extra round trips beyond the explicit user-triggered re-estimate. useState for two strings — negligible.

Tests

  • PR description acknowledges no automated verification (lint/tsc not run in isolated worktree). Given current project posture (no unit tests for these components), not a blocker — but the Tailwind regression would have been caught by a manual visual smoke. Manual verification steps listed in PR body are reasonable; please add a 4th step: «убедиться что плашка имеет оранжевую заливку и рамку» so post-deploy QA notices if the styling stays bare.

Pre-flight checks

  • Branch ≠ main
  • No --no-verify / --amend / --force in recent commits
  • Commit history clean (765b172 on top of e45ff47)
  • Diff matches description

  1. Replace Tailwind classes in HeroSummary.tsx with inline styles using existing --accent-2* / --surface-2 tokens (or add .hero-disclaimer / .hero-enrich to trade-in.css).
  2. Add role="status" aria-live="polite" to the disclaimer wrapper.
  3. Optional: move handleResubmit below resultData declaration in page.tsx.
  4. Re-push; review/merge unblocks.

Complexity / blast radius

  • Risk: low — UI only, single tradein page.
  • Reversibility: trivial revert.
  • Merge window: any (no DB / API changes).
<!-- gendesign-review-bot: sha=765b172 verdict=changes --> ## Deep Code Review — verdict: REQUEST CHANGES **Summary** - Status: 🟠 High (1) + 🟡 Medium / Low (3) - Files: 3 (P1: HeroSummary.tsx, P2: page.tsx + DealsCard.tsx) - Lines: +113 / -19 · PR: #517 - Vault cross-check: matches `Decision_TradeIn_DataQuality_8PR_Roadmap` items #6 (mock disclaimer) + #11 (progressive enrichment). Scope correct. --- ### 🟠 High — Tailwind classes used but Tailwind NOT installed in `tradein-mvp/frontend` **File:** `tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx` Two new blocks use Tailwind utility classes: 1. **Mock disclaimer (lines ~104-109):** ``` className="rounded-lg border border-amber-500/40 bg-amber-50 px-4 py-3 text-sm text-amber-900" ``` 2. **Enrichment form wrapper (line ~325):** ``` className="rounded-lg border border-slate-200 bg-slate-50 p-4 space-y-3" ``` plus `text-sm font-medium`, `control` (custom), `btn btn-primary` (custom — these work). **Problem:** `tradein-mvp/frontend/package.json` has NO `tailwindcss` dependency. There is no `tailwind.config.{js,ts}`, no `postcss.config.*`, no `@tailwind` directive in `globals.css` or `trade-in.css`. The whole subproject uses semantic class names (`card`, `card-head`, `meta-row`, `hero-card`, …) backed by `trade-in.css` with OKLCH design tokens. **Effect:** all Tailwind classes are inert. The mock disclaimer (THE headline feature of this PR — per roadmap #6) will render as **plain unstyled text on the card surface** — no amber border, no warning-background, no padding (only `margin: 16px 16px 0` inline applies). That's a functional UX regression of the feature itself: a non-styled warning is no warning. **Fix options:** *Option A — inline styles (minimal diff, matches existing patterns in this file):* ```tsx {showMockDisclaimer && ( <div role="status" aria-live="polite" style={{ margin: "16px 16px 0", padding: "12px 16px", border: "1px solid var(--accent-2)", background: "var(--accent-2-soft)", color: "var(--fg)", borderRadius: "var(--radius)", fontSize: 13, }} > Адрес распознан неточно — оценка может быть приближённой. Уточните адрес и пересчитайте для более точного результата. </div> )} ``` *Option B — add CSS classes to `trade-in.css`:* `.hero-disclaimer`, `.hero-enrich`, `.hero-enrich select`, etc. — preferred for reuse and theme consistency. The file already defines `--accent-2` / `--accent-2-soft` (orange/warn) which is the natural choice for this disclaimer. Apply the same to the enrichment form block. Pick one approach but **don't ship Tailwind classes here**. --- ### 🟡 Medium — Mock disclaimer not announced by screen readers **File:** `HeroSummary.tsx` ~104 The disclaimer appears conditionally after async data load. Without `role="status"` + `aria-live="polite"` (or `role="alert"` for stronger urgency), AT users won't be notified that the estimate is approximate. Add to the wrapper `<div>` (see snippet above). --- ### 🟢 Low — `handleResubmit` declared before `resultData` **File:** `tradein-mvp/frontend/src/app/page.tsx` lines 62-65 reference `resultData` defined at line 69. Runtime-safe (callback fires after render completes; TDZ won't trip), but ESLint `no-use-before-define` may flag this. PR description acknowledges lint never ran in the isolated worktree. Consider moving the `handleResubmit` declaration AFTER `resultData`, or convert `resultData` to a `useMemo`/`useState` hoisted above. --- ### 🟢 Low — Disclaimer wording diverges from roadmap Roadmap (`Decision_TradeIn_DataQuality_8PR_Roadmap` row #6) specifies «Адрес не распознан — оценка примерная (mock-данные)». PR uses «Адрес распознан неточно — … приближённой». The PR phrasing is softer and arguably better UX, but it drops the explicit «mock-данные» marker. Worth a quick alignment ping with product. **Not a blocker.** --- ### Cross-file impact (verified) - `DealsCard.tsx`: returning `null` when `actual_deals.length === 0` — no caller iterates over the rendered output; `page.tsx` renders `<DealsCard … />` inline, so a null render is harmless. ✅ - `page.tsx` `handleResubmit`: correctly merges `resultData.input + patch` and re-runs `handleSubmit`. URL replace + `freshResult` overwrite afterward — TanStack mutation queues on the same key; double-submit guarded by `isResubmitting` disabled state. ✅ - `SourcesProgress` consumes the old `estimate` during resubmit pending — no flash-of-mocked-content because `resultData` only switches in `onSuccess`. ✅ - Types `HouseType` / `RepairState` re-exported from `@/types/trade-in` — matches existing schema. ✅ - `est_days_on_market` field exists on `AggregatedEstimate` and may be `null` — handled. ✅ ### Security / XSS (per spec focus) - Disclaimer text is hardcoded; no user content rendered as HTML. - Enrichment values come from controlled `<select>` whose `<option value>` exactly matches the `HouseType` / `RepairState` union — the `as HouseType` cast is safe. - No `safeUrl` usage needed — no URLs constructed from user input in this PR. - No new external resource loading. ✅ ### Performance - No N+1, no extra round trips beyond the explicit user-triggered re-estimate. `useState` for two strings — negligible. ✅ ### Tests - PR description acknowledges no automated verification (lint/tsc not run in isolated worktree). Given current project posture (no unit tests for these components), not a blocker — but the Tailwind regression would have been caught by a manual visual smoke. Manual verification steps listed in PR body are reasonable; please add a 4th step: «убедиться что плашка имеет оранжевую заливку и рамку» so post-deploy QA notices if the styling stays bare. ### Pre-flight checks - Branch ≠ main ✅ - No `--no-verify` / `--amend` / `--force` in recent commits ✅ - Commit history clean (765b172 on top of e45ff47) ✅ - Diff matches description ✅ --- ### Recommended next steps 1. Replace Tailwind classes in `HeroSummary.tsx` with inline styles using existing `--accent-2*` / `--surface-2` tokens (or add `.hero-disclaimer` / `.hero-enrich` to `trade-in.css`). 2. Add `role="status" aria-live="polite"` to the disclaimer wrapper. 3. Optional: move `handleResubmit` below `resultData` declaration in `page.tsx`. 4. Re-push; review/merge unblocks. ### Complexity / blast radius - Risk: low — UI only, single tradein page. - Reversibility: trivial revert. - Merge window: any (no DB / API changes).
lekss361 added 1 commit 2026-05-24 12:47:58 +00:00
Review-bot feedback on #517:
- tradein-mvp/frontend doesn't use Tailwind — classes (rounded-lg, bg-amber-50, …)
  rendered as plain unstyled text. Replace with inline styles using project's
  OKLCH design tokens (--accent-2, --accent-2-soft, --surface-2, --radius, --fg).
- Add role=status aria-live=polite on disclaimer for screen readers
- Move handleResubmit below resultData declaration (ESLint no-use-before-define)
Author
Owner

Deep code review — PR #517 (fresh independent re-review)

Note on SHA: Prior review targeted commit 765b172. PR HEAD has since advanced to 81dd103c ("fix(tradein-ui): replace Tailwind with OKLCH tokens + a11y on disclaimer"). This review evaluates the current PR head (cumulative state).

Prior findings — all resolved

# Prior issue Status at 81dd103c
1 Tailwind classes in tradein-mvp/frontend (no Tailwind installed) → unstyled disclaimer Fixed — replaced with inline style={{}} using OKLCH design tokens (--accent-2, --accent-2-soft, --fg, --radius, --border, --surface-2, --muted). All tokens verified to exist in tradein-mvp/frontend/src/components/trade-in/trade-in.css (lines 1-42). Confirmed no Tailwind: package.json has no tailwindcss; Dockerfile line 2 explicitly states «Без Sentry/Tailwind/Leaflet/echarts».
2 Missing aria-live on dynamic warning Fixed — disclaimer now has role="status" aria-live="polite" (HeroSummary.tsx ~line 105)
3 handleResubmit references resultData before its declaration Fixed — handleResubmit moved to line 78 of page.tsx, after resultData declaration (line 75)

New finding from fresh review

🟡 Medium — Restored-estimate path: resubmit POSTs invalid area_m2: 0

When the user navigates via ?id=<uuid> (restored estimate), resultData.input is a stub:

{ address: ..., area_m2: 0, rooms: ..., floor: 0, total_floors: 0 }

Clicking «Пересчитать» in the enrichment form calls handleResubmit({house_type|repair_state}) which spreads this stub into the request body. Backend schema requires area_m2: float = Field(gt=10, lt=500) → POST returns 422.

The primary form-driven (fresh) path is unaffected and works correctly — the bug is only on the secondary restored-by-?id= path. AggregatedEstimate already carries authoritative area_m2 / floor / total_floors / rooms / year_built / has_balcony (types/trade-in.ts L70-77), so a one-liner backfill in handleResubmit resolves it. Not a release-blocker but worth a quick follow-up commit before reliance grows.

Suggested patch:

function handleResubmit(patch: { house_type?: HouseType; repair_state?: RepairState }) {
  if (!resultData) return;
  const e = resultData.estimate;
  const enrichedInput: TradeInEstimateInput = {
    ...resultData.input,
    address: e.target_address ?? resultData.input.address,
    area_m2: e.area_m2 ?? resultData.input.area_m2,
    rooms: e.rooms ?? resultData.input.rooms,
    floor: e.floor ?? resultData.input.floor,
    total_floors: e.total_floors ?? resultData.input.total_floors,
    year_built: e.year_built ?? resultData.input.year_built,
    has_balcony: e.has_balcony ?? resultData.input.has_balcony,
    ...patch,
  };
  handleSubmit(enrichedInput);
}

Smaller observations (not blocking)

  • showEnrichment semantic question(needsHouseType || needsRepairState) && estimate.confidence !== "low" hides enrichment when confidence is low. Counter-intuitive at first glance (you'd think low confidence is exactly when you'd want enrichment offered), but plausibly intentional: low confidence often means address resolution failed, where enrichment won't help. Worth confirming design intent.
  • Enum castenrichHouseType as HouseType is guarded by truthy check; state is typed string. Would be cleaner as useState<HouseType | "">(""), but current form is safe.
  • isMockAddress heuristic — hardcoded regex list of "Склад/Гараж/Подсобка/Нежилое" + confidence_explanation.startsWith("address_not_geocoded") is acceptable as a frontend heuristic for MVP. Long-term: backend should return an explicit target_address_is_fallback: bool flag.
  • DealsCard returning null on empty deals is intentional UX cleanup; section disappears entirely. Confirmed no regression: SourcesProgress and HeroSummary's actual_deals price bar both gate independently.

Verdict

Approve. All three prior blocking issues are correctly addressed in 81dd103c. The restored-path 422 issue is a real bug but on a secondary code path that doesn't block the primary feature flow; recommend a follow-up commit.

<!-- gendesign-review-bot: sha=765b172 verdict=approve --> ## Deep code review — PR #517 (fresh independent re-review) **Note on SHA:** Prior review targeted commit `765b172`. PR HEAD has since advanced to `81dd103c` ("fix(tradein-ui): replace Tailwind with OKLCH tokens + a11y on disclaimer"). This review evaluates the **current PR head** (cumulative state). ### Prior findings — all resolved | # | Prior issue | Status at `81dd103c` | |---|---|---| | 1 | Tailwind classes in `tradein-mvp/frontend` (no Tailwind installed) → unstyled disclaimer | Fixed — replaced with inline `style={{}}` using OKLCH design tokens (`--accent-2`, `--accent-2-soft`, `--fg`, `--radius`, `--border`, `--surface-2`, `--muted`). All tokens verified to exist in `tradein-mvp/frontend/src/components/trade-in/trade-in.css` (lines 1-42). Confirmed no Tailwind: `package.json` has no tailwindcss; `Dockerfile` line 2 explicitly states «Без Sentry/Tailwind/Leaflet/echarts». | | 2 | Missing `aria-live` on dynamic warning | Fixed — disclaimer now has `role="status" aria-live="polite"` (HeroSummary.tsx ~line 105) | | 3 | `handleResubmit` references `resultData` before its declaration | Fixed — `handleResubmit` moved to line 78 of `page.tsx`, after `resultData` declaration (line 75) | ### New finding from fresh review **🟡 Medium — Restored-estimate path: resubmit POSTs invalid `area_m2: 0`** When the user navigates via `?id=<uuid>` (restored estimate), `resultData.input` is a stub: ```ts { address: ..., area_m2: 0, rooms: ..., floor: 0, total_floors: 0 } ``` Clicking «Пересчитать» in the enrichment form calls `handleResubmit({house_type|repair_state})` which spreads this stub into the request body. Backend schema requires `area_m2: float = Field(gt=10, lt=500)` → POST returns 422. The primary form-driven (fresh) path is unaffected and works correctly — the bug is only on the secondary restored-by-`?id=` path. `AggregatedEstimate` already carries authoritative `area_m2 / floor / total_floors / rooms / year_built / has_balcony` (types/trade-in.ts L70-77), so a one-liner backfill in `handleResubmit` resolves it. Not a release-blocker but worth a quick follow-up commit before reliance grows. Suggested patch: ```ts function handleResubmit(patch: { house_type?: HouseType; repair_state?: RepairState }) { if (!resultData) return; const e = resultData.estimate; const enrichedInput: TradeInEstimateInput = { ...resultData.input, address: e.target_address ?? resultData.input.address, area_m2: e.area_m2 ?? resultData.input.area_m2, rooms: e.rooms ?? resultData.input.rooms, floor: e.floor ?? resultData.input.floor, total_floors: e.total_floors ?? resultData.input.total_floors, year_built: e.year_built ?? resultData.input.year_built, has_balcony: e.has_balcony ?? resultData.input.has_balcony, ...patch, }; handleSubmit(enrichedInput); } ``` ### Smaller observations (not blocking) - **showEnrichment semantic question** — `(needsHouseType || needsRepairState) && estimate.confidence !== "low"` hides enrichment when confidence is `low`. Counter-intuitive at first glance (you'd think low confidence is exactly when you'd want enrichment offered), but plausibly intentional: low confidence often means address resolution failed, where enrichment won't help. Worth confirming design intent. - **Enum cast** — `enrichHouseType as HouseType` is guarded by truthy check; state is typed `string`. Would be cleaner as `useState<HouseType | "">("")`, but current form is safe. - **`isMockAddress` heuristic** — hardcoded regex list of "Склад/Гараж/Подсобка/Нежилое" + `confidence_explanation.startsWith("address_not_geocoded")` is acceptable as a frontend heuristic for MVP. Long-term: backend should return an explicit `target_address_is_fallback: bool` flag. - **DealsCard returning `null` on empty deals** is intentional UX cleanup; section disappears entirely. Confirmed no regression: `SourcesProgress` and `HeroSummary`'s actual_deals price bar both gate independently. ✅ ### Verdict **Approve.** All three prior blocking issues are correctly addressed in `81dd103c`. The restored-path 422 issue is a real bug but on a secondary code path that doesn't block the primary feature flow; recommend a follow-up commit.
lekss361 merged commit 92d00ec874 into main 2026-05-24 12:58:39 +00:00
lekss361 deleted branch feat/tradein-ui-mock-disclaimer-enrichment 2026-05-24 12:58:40 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#517
No description provided.