From 5640720edbbb729ff48235a46ba029ca0a7127f0 Mon Sep 17 00:00:00 2001 From: Mario Mohar Date: Mon, 14 Sep 2026 10:39:18 +0200 Subject: [PATCH] feat: warn as the description fills up Closes #512. The issue asks for a colour warning as the description approaches its limit. Three things stood in the way of writing that as described: `descCount` does not exist. The count was computed inline, and twice in a row, once for the number and once for the plural of "word". The counter shows words, not characters, while the issue formats `{descCount}/300` as if it were characters. And there was no limit to approach. The textarea had no `maxLength`, and `description: { type: String, default: "" }` in the Task model has no constraint either. So this adds the missing piece first: a `DESCRIPTION_MAX_LENGTH` next to the `TITLE_MAX_LENGTH` the title already uses, and the same `maxLength` on the field. 500 rather than the 300 from the issue, because the AI button asks Gemini for "2-3 sentences" and that lands around 150 to 400 characters. At 300 the field would cut off the text the button had just produced. The word count stays where it was. Next to it sits the character budget, in the theme's muted colour, yellow from 80 percent and red from 92 percent. Both thresholds are fractions of the constant, so they follow if the budget is ever changed. Note that `maxLength` only limits typing and pasting. The AI generator writes into the field programmatically, so a longer answer still lands there and shows over budget in red rather than being silently cut. --- .../client/src/components/Task/TaskModal.jsx | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/devboard/client/src/components/Task/TaskModal.jsx b/devboard/client/src/components/Task/TaskModal.jsx index b81cf6e..61372c2 100644 --- a/devboard/client/src/components/Task/TaskModal.jsx +++ b/devboard/client/src/components/Task/TaskModal.jsx @@ -4,6 +4,14 @@ import { useBoard } from "../../context/BoardContext"; import { QRCodeSVG } from "qrcode.react"; const TITLE_MAX_LENGTH = 100; +// The AI generator asks Gemini for "2-3 sentences", which lands around 150 to +// 400 characters. A budget has to hold that plus a note typed afterwards, +// otherwise the field cuts off the text the button just produced. +const DESCRIPTION_MAX_LENGTH = 500; +// Where the counter starts warning. Kept as fractions so both follow the +// budget above instead of drifting when it changes. +const DESCRIPTION_WARN_AT = Math.round(DESCRIPTION_MAX_LENGTH * 0.8); +const DESCRIPTION_ALERT_AT = Math.round(DESCRIPTION_MAX_LENGTH * 0.92); const COPY_SUFFIX = " (copy)"; const COLORS = [ @@ -48,6 +56,14 @@ const TaskModal = ({ t.title?.toLowerCase().trim() === form.title.toLowerCase().trim() && t._id !== task?._id, ); + // Both counters were computed inline before, the word count twice in a row: + // once for the number and once for the plural of "word". + const descriptionLength = form.description.length; + const descriptionWords = form.description + .trim() + .split(/\s+/) + .filter(Boolean).length; + // Tags are stored the way they were typed, so the list keeps the first // spelling it sees and matches case insensitively: typing "rea" should still // find an existing "React" rather than nothing. @@ -349,6 +365,7 @@ const TaskModal = ({