Let a card be read: today a card's title lives in a single-line <input> in a 16rem column, so most of it is off-screen unless you click in and scroll.
Measured on main at b74399a, locally: a card titled with 208 characters (the app's cap is 200) renders as <input name="title" value="…"> inside .column { width: 16rem } with .card .edit input { width: 100% }. About 30 characters are visible; the rest is reachable only by focusing the field and holding an arrow key. Nothing wraps, nothing truncates with an ellipsis, and there is no tooltip — a board of long titles is a board of identical-looking cards. This is the first thing a person notices on a real board and nothing open names it (#49 adds a note, #88 handles two people editing at once, #48 moves the Move control — none of them make the title readable).
Done looks like
[ ] A card's title wraps and is fully visible on the board, at any length up to the 200-character cap, with no tooltip needed and no horizontal overflow (overflow-wrap: anywhere for a pasted URL with no spaces).
[ ] Editing stays in place and stays one keystroke away: the title is a wrapping control (a textarea sized to its content is the straightforward way), Enter saves, Escape restores what was there, and the drag paths keep ignoring presses that start inside it — pointerdown already skips input, select, button, textarea, a.
[ ] The no-JavaScript path still saves: with a textarea, Enter no longer submits the form for free, so the edit form carries a real submit control that a keyboard reaches. Say in the pull what it costs per card in bytes, measured before and after, since #48 is shrinking the same markup.
[ ] A title stays one line of text in the database: cleanText in src/routes.ts (and the MCP tools' own validation) collapses every run of whitespace, newlines included, to a single space before it is stored, so a pasted paragraph cannot change a card's height by surprise. A test posts "a\nb" and reads back "a b".
[ ] No layout shift: the control's height comes from the server-rendered markup — rows computed from the title's length by a pure function next to the renderer, tested — not from a script that resizes after the first paint.
[ ] Everything that reads a card still works: the filter reads data-title as it does now, buildCard in static/board.js builds the same markup as the server, the keyboard move and the live card.edited branch both still find the field, and the shortcuts dialog's wording matches what Enter now does.
Files I expect to change:src/pages/board.ts (cardHtml, styles, boardShortcuts, BOARD_JS), static/board.<hash>.js (renamed — buildCard, the edit submit handler, the live card.edited branch), src/routes.ts (cleanText), src/pages/board.test.ts, src/server.test.ts, src/drag.test.ts, README.md.
Risks. The keyboard contract changes shape: Enter inside a textarea normally inserts a newline, so intercepting it has to be deliberate and the help dialog has to agree. Taller cards make a long column longer — that is the point, but check it against the phone width #95 is working on. Leave whatever hidden field #88 adds to the same form alone.
Waits for the failed-write issue above, which rewrites the same submit handlers in static/board.js; that one waits for #48.
Let a card be read: today a card's title lives in a single-line
<input>in a 16rem column, so most of it is off-screen unless you click in and scroll.Measured on
mainatb74399a, locally: a card titled with 208 characters (the app's cap is 200) renders as<input name="title" value="…">inside.column { width: 16rem }with.card .edit input { width: 100% }. About 30 characters are visible; the rest is reachable only by focusing the field and holding an arrow key. Nothing wraps, nothing truncates with an ellipsis, and there is no tooltip — a board of long titles is a board of identical-looking cards. This is the first thing a person notices on a real board and nothing open names it (#49 adds a note, #88 handles two people editing at once, #48 moves the Move control — none of them make the title readable).Done looks like
overflow-wrap: anywherefor a pasted URL with no spaces).textareasized to its content is the straightforward way), Enter saves, Escape restores what was there, and the drag paths keep ignoring presses that start inside it —pointerdownalready skipsinput, select, button, textarea, a.textarea, Enter no longer submits the form for free, so the edit form carries a real submit control that a keyboard reaches. Say in the pull what it costs per card in bytes, measured before and after, since #48 is shrinking the same markup.cleanTextinsrc/routes.ts(and the MCP tools' own validation) collapses every run of whitespace, newlines included, to a single space before it is stored, so a pasted paragraph cannot change a card's height by surprise. A test posts"a\nb"and reads back"a b".data-titleas it does now,buildCardinstatic/board.jsbuilds the same markup as the server, the keyboard move and the livecard.editedbranch both still find the field, and the shortcuts dialog's wording matches what Enter now does.Files I expect to change:
src/pages/board.ts(cardHtml,styles,boardShortcuts,BOARD_JS),static/board.<hash>.js(renamed —buildCard, the edit submit handler, the livecard.editedbranch),src/routes.ts(cleanText),src/pages/board.test.ts,src/server.test.ts,src/drag.test.ts,README.md.Risks. The keyboard contract changes shape: Enter inside a
textareanormally inserts a newline, so intercepting it has to be deliberate and the help dialog has to agree. Taller cards make a long column longer — that is the point, but check it against the phone width #95 is working on. Leave whatever hidden field #88 adds to the same form alone.Waits for the failed-write issue above, which rewrites the same submit handlers in
static/board.js; that one waits for #48.Suggested: scout, reviewed by juniper.