Public: anyone can read this; the crew can change it.
Fix a flagged Fair Housing phrase in every asset at once: one tap puts the suggested words in place wherever the phrase appears, and each changed asset waits for approval again #126
Check · Review · Land · Live done — Landed and live in production.
The listing page now lists each flagged Fair Housing phrase once, and one button per phrase puts the suggested words into every asset at once. On the Birchwood sample, three taps take the listing from 22 findings to clear, and a test proves it. I ran the related suites (121 tests, all pass) and `lint-design`. I did not run the full `bun test` or the type check. `bun x tsc --noEmit` only printed it…
shipped production is serving it deploy 957b843
The listing page now lists each flagged Fair Housing phrase once, and one button per phrase puts the suggested words into every asset at once. On the Birchwood sample, three taps take the listing from 22 findings to clear, and a test proves it. I ran the related suites (121 tests, all pass) and lint-design. I did not run the full bun test or the type check. bun x tsc --noEmit only printed its help text, because the repo has no tsconfig.json. There is no scripts/gate.ts or scripts/fast.ts.
What changed
- Summary: a "Fair Housing: N phrases to review" card sits above the tabs. Each phrase shows how many assets it is in and the words that replace it. A phrase with only advice, and no replacement words, shows no button. Readers who cannot edit see the list without buttons.
- Counts: the sample's 22 findings are three phrases in 7, 6 and 6 assets (the issue guessed eight assets). The test uses the real counts.
- Button: "Use the suggestion everywhere" is a same-origin POST to
/listings/:id/fair-housing/suggestion. It callsuseSuggestionEverywhereinsrc/actions.ts, which goes through the samelistingForandpermitcheck asuseSuggestion, so no contract cell changed. - What it does: in one transaction, every asset carrying the phrase is rewritten, its old words go to version history (reason "suggestion used everywhere"), and its approval is cleared. Nothing becomes approved. The page says how many assets changed and that each needs approval again.
- Audit and refusals: one audit entry is written per changed asset. A phrase that is no longer present is a
conflictand changes nothing. - Replacement mechanics: replacements run from the end of each asset so offsets stay valid. Replacement words that start a sentence keep their capital, but the single-suggestion route does not; it's unchanged.
- Added replacement words: "Walking distance to church" and "Great for young couples" had no replacement words, so one tap could not clear them. I gave "Walking distance to church" the drop-in "near shops, parks and transit". I split "great for couples" into its own rule with the drop-in "an easy, flexible layout"; the other household phrases ("perfect for a young family") stay advice-only. The wording is my choice, and some replacements read a little awkwardly (for example "an easy, flexible layout starting out"). A person approves every asset anyway, but you may want better words.
- README: the Birchwood step mentions the new button.
- Tests:
src/everywhere.test.tscovers the points in the issue's checklist, including the refusal wording matching the single-suggestion route. It also checks that the page shows the button only to someone who may edit.
Not done: I did not add the button to the MCP tools or the terminal, since the issue only asks for the page. There is no JavaScript swap for it; it is a plain form that reloads the page with a notice.
Try it: open 27 Birchwood Court, Boise signed in as its author, press "Use the suggestion everywhere" three times, and approve each asset again.
Core logic is sound (permit via listingFor, one transaction, approval cleared, versions kept, audit per asset, 3 taps clear Birchwood). Changes needed, all in one round:
- FIRST: the terminal door is missing. The page can take a suggestion everywhere; src/tty.ts cannot (it has
ufor the single suggestion). Add a key in tty.ts that calls useSuggestionEverywhere for the selected finding's phrase and shows the 'Changed N assets; each needs approval again' notice, add it to the help line, and say so in the README. Both doors is a hard requirement; 'the issue only asks for the page' does not exempt it. The MCPuse_suggestiontool has the same gap: add an everywhere variant or state it in the README. - Tests: the 'refused as on the single route' test is convoluted (dynamic import inside a promise chain) and the approve call passes a stray
...{ tooLong: true }; clean both. Add a test for the route (cross-origin/guest POST refused, notice text) and for the terminal key once added. - The audit records are written outside the transaction; record them inside it so a failure cannot leave changes unaudited.
- The drop-in 'an easy, flexible layout' produces awkward copy ('An easy, flexible layout starting out'); pick wording that reads in the sample's sentences.
All four of sentinel's points are addressed and pushed to the same branch. The related suites pass (everywhere, walkthrough, server, tty, contract, audit and security), plus lint-design. I did not run the full bun test, and bun x tsc --noEmit is still unusable because the repo has no tsconfig.json.
- Terminal and MCP doors:
- Terminal: U takes the first suggestible phrase on the asset under the cursor in every asset of the listing. It shows the same "Changed N assets; each needs approval again." line as the page. That line is now one shared changedLine, and the help line lists U use it in every asset.
- MCP: there is a new use_suggestion_everywhere tool taking { id, phrase }, and server.test.ts's tool list includes it.
- README: both are documented in the suggestions paragraph. The terminal and MCP mentions stay out of the five-minute walkthrough, because walkthrough.test.ts rejects the word "MCP" there.
- Tests:
- The refusal test now uses a static useSuggestion import and compares both the error and the message with the everywhere action's. The stray tooLong is gone.
- New tests cover the route, the terminal key and the MCP tool. The route test checks that a cross-origin POST gets 403, a guest POST is refused, and the owner gets the redirect and the notice text.
- Audit: the per-asset audit entries are now written inside the transaction, next to each change.
- Wording: the "couples" rule now also matches a trailing "starting out" (or "just starting out"), and its replacement is "an easy, flexible layout". The sample sentence becomes "An easy, flexible layout." instead of "An easy, flexible layout starting out."
The Birchwood phrase is now named "Great for young couples starting out" rather than the issue's "Great for young couples". It is still one phrase, in the same six assets, and the three taps still take the sample from 22 findings to clear.
The existing "family-friendly" replacement is unchanged, so the sample reads "Near parks and schools street with sidewalks…". That is a little awkward, but it was already how the single-suggestion button worked, so I left it alone.
All earlier asks are met. The terminal (U) and MCP (use_suggestion_everywhere) doors exist and go through the same listingFor/permit check as the page. The refusal test now compares error and message with useSuggestion. Route, terminal and MCP tests were added. Audit entries are written inside the transaction, one per changed asset. The couples wording is fixed. No contract cell changed. The phrase is capped at 200 characters and escaped in the form. The route is a same-origin POST, and the cross-origin and guest cases are tested. Nothing is approved by the action. The clone does not have head 8b817e3abb1b, so I read the full diff rather than the delta. Non-blocking: the terminal test's 22 - 8 - 0 arithmetic is opaque; a named constant would read better.
Left #121 open: 1 unticked item remain (Each phrase has one button, "Use the suggestion everywhere". It is a same-origin POST, and only the listing's author or an admin may use it (asked through permit, the same as useSuggestion). It replaces that phrase with its suggestion in every asset of the listing in one transaction and re-runs the check on each one.).
The listing page now lists each flagged Fair Housing phrase once, and one button per phrase puts the suggested words into every asset at once. On the Birchwood sample, three taps take the listing from 22 findings to clear, and a test proves it. I ran the related suites (121 tests, all pass) and
lint-design. I did not run the fullbun testor the type check.bun x tsc --noEmitonly printed its help text, because the repo has notsconfig.json. There is noscripts/gate.tsorscripts/fast.ts.What changed
/listings/:id/fair-housing/suggestion. It callsuseSuggestionEverywhereinsrc/actions.ts, which goes through the samelistingForandpermitcheck asuseSuggestion, so no contract cell changed.conflictand changes nothing.src/everywhere.test.tscovers the points in the issue's checklist, including the refusal wording matching the single-suggestion route. It also checks that the page shows the button only to someone who may edit.Not done: I did not add the button to the MCP tools or the terminal, since the issue only asks for the page. There is no JavaScript swap for it; it is a plain form that reloads the page with a notice.
Try it: open 27 Birchwood Court, Boise signed in as its author, press "Use the suggestion everywhere" three times, and approve each asset again.
Closes #121