Public: anyone can read this; the crew can change it.

Fix a Fair Housing flag in one tap with the suggested wording #20

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Each Fair Housing finding with a drop-in rewrite now has a **Use "…"** button. The author or an admin sees it in the aside on the listing page. The listing, fair-housing, contract, security, design, openapi, walkthrough, tty, server, audit and pages tests pass, and `lint-design` is clean. I did not run `tsc` (see the end) or try the browser path in `static/listing.js`.

shipped production is serving it deploy 957b843

opened this

Each Fair Housing finding with a drop-in rewrite now has a Use "…" button. The author or an admin sees it in the aside on the listing page. The listing, fair-housing, contract, security, design, openapi, walkthrough, tty, server, audit and pages tests pass, and lint-design is clean. I did not run tsc (see the end) or try the browser path in static/listing.js.

What changed

  • Rules: src/fairhousing.ts gives a rule an optional dropIn phrase and every Finding carries dropIn (null when absent). Six rules have one:

- family-friendly → "near parks and schools" - kid-friendly / child-friendly → "near parks and schools" - family neighborhood and similar → "quiet street near parks" - near a church and similar → "near shops, parks and transit" - mother-in-law suite → "guest suite" - man cave → "den"

  • Action: useSuggestion in src/actions.ts takes the listing, asset kind, start, end and the flagged text.

- It checks that the stored asset still has a finding at exactly that place with exactly those words; otherwise it fails with conflict and changes nothing. - It replaces only that span and saves through the existing edit path, so the check reruns, approval is cleared and the audit row is written under listing:update. - Permission goes through listingFor/permit, so a non-author gets 403. A finding with no dropIn is refused with invalid.

  • Route: POST /listings/:id/assets/:kind/suggestion in src/routes.ts. A plain form POST redirects back to the asset with a notice. With accept: application/json it returns the new text, the aside HTML and the approval control.
  • Page: src/pages/listing.ts renders one small form per finding, only for someone who may edit. The live typing check passes no buttons, because unsaved text doesn't match what is stored.
  • JS: static/listing.js intercepts the form, then updates the textarea, aside, character count and approval control in place. A refusal shows in the aside. Without the script the form posts normally.
  • Tests:

- src/listings.test.ts: the replacement lands, a stale offset or wrong text gives 409, a non-author gets 403, a finding with no dropIn gives 422, approval is cleared, the audit outcomes appear, and the no-JS redirect works. - src/fairhousing.test.ts: which findings carry a dropIn.

What I chose not to do

  • No button for advice rules, or where the rewrite would change meaning: master bath/master suite → "primary bedroom", the worship-distance rule, and the buyer-description rules.
  • No contract change.
  • I added no dependency.

How to try it: open a listing as its agent and save "A family-friendly street." in any asset. The aside shows Use "near parks and schools"; tapping it rewrites the words and clears the approval.

Not run: bun x tsc --noEmit. The clone has no tsconfig.json or node_modules, so tsc only printed its usage. There is no scripts/gate.ts or scripts/fast.ts either, so I ran the test files that cover this change by name.

Closes #9

commented

Each Fair Housing finding with a drop-in rewrite now has a Use "…" button. The author or an admin sees it in the aside on the listing page. The listing, fair-housing, contract, security, design, openapi, walkthrough, tty, server, audit and pages tests pass, and lint-design is clean. I did not run tsc (see the end) or try the browser path in static/listing.js.

What changed

  • Rules: src/fairhousing.ts gives a rule an optional dropIn phrase and every Finding carries dropIn (null when absent). Six rules have one:

- family-friendly → "near parks and schools" - kid-friendly / child-friendly → "near parks and schools" - family neighborhood and similar → "quiet street near parks" - near a church and similar → "near shops, parks and transit" - mother-in-law suite → "guest suite" - man cave → "den"

  • Action: useSuggestion in src/actions.ts takes the listing, asset kind, start, end and the flagged text.

- It checks that the stored asset still has a finding at exactly that place with exactly those words; otherwise it fails with conflict and changes nothing. - It replaces only that span and saves through the existing edit path, so the check reruns, approval is cleared and the audit row is written under listing:update. - Permission goes through listingFor/permit, so a non-author gets 403. A finding with no dropIn is refused with invalid.

  • Route: POST /listings/:id/assets/:kind/suggestion in src/routes.ts. A plain form POST redirects back to the asset with a notice. With accept: application/json it returns the new text, the aside HTML and the approval control.
  • Page: src/pages/listing.ts renders one small form per finding, only for someone who may edit. The live typing check passes no buttons, because unsaved text doesn't match what is stored.
  • JS: static/listing.js intercepts the form, then updates the textarea, aside, character count and approval control in place. A refusal shows in the aside. Without the script the form posts normally.
  • Tests:

- src/listings.test.ts: the replacement lands, a stale offset or wrong text gives 409, a non-author gets 403, a finding with no dropIn gives 422, approval is cleared, the audit outcomes appear, and the no-JS redirect works. - src/fairhousing.test.ts: which findings carry a dropIn.

What I chose not to do

  • No button for advice rules, or where the rewrite would change meaning: master bath/master suite → "primary bedroom", the worship-distance rule, and the buyer-description rules.
  • No contract change.
  • I added no dependency.

How to try it: open a listing as its agent and save "A family-friendly street." in any asset. The aside shows Use "near parks and schools"; tapping it rewrites the words and clears the approval.

Not run: bun x tsc --noEmit. The clone has no tsconfig.json or node_modules, so tsc only printed its usage. There is no scripts/gate.ts or scripts/fast.ts either, so I ran the test files that cover this change by name.

Changes requested — before the latest push

Core logic is sound (permit via listingFor, stale-place conflict, audited, approval cleared, tests good). Changes needed: 1) FIRST: no second door. The tap exists only on the page; src/tty.ts and src/mcp.ts have no way to take a suggestion (they offer editAsset/approveAsset). Add a tty command and an MCP tool calling useSuggestion, with tests, and a row in src/contract.test.ts for the new door like editAsset/approveAsset have. 2) README says nothing about the one-tap suggestion; add how to use it on page and terminal. 3) Minor cleanliness: import of useSuggestion in routes.ts is out of alphabetical order; .fh-fix CSS appended after the print block rather than with the fh rules.

commented

The suggestion now has a terminal door and an MCP door as well as the page, with tests. The README and the CSS placement are also fixed. The contract, tty, server, listings, openapi, walkthrough and security tests pass, and lint-design is clean. I did not run tsc (the clone has no tsconfig).

  • Terminal: u on an open listing takes the first suggestion on the asset under the cursor, through useSuggestion. It says when nothing there has a drop-in phrase, and a non-author is refused with the same message as for e. The help line lists it, and src/tty.test.ts covers all three cases.
  • MCP: a new use_suggestion tool takes { id, kind, start, end, text } and calls useSuggestion. src/server.test.ts now lists it among the tools.
  • Contract: src/contract.test.ts has a new test that runs use_suggestion through the action and the MCP tool as every identity. It checks that both let through exactly whom the listing:update cell does, so there is no new cell. It is a separate test, not a row in doors, because that map holds one door per verb and approveAsset is covered the same way.
  • README: it now explains the button on the page, the plain-POST route, the MCP tool and u in the terminal.
  • Tidy: the useSuggestion import in src/routes.ts is in alphabetical order, and .fh-fix now sits with the other .fh-* rules, before .fh-text.

I did not add a JSON API route for it. The page route already answers JSON when asked, and the issue named only the page, the MCP and the terminal.

Approved

Earlier asks are met: the terminal door (u, tested incl. guest refusal), the MCP use_suggestion tool, a contract test running it against the listing:update cell, README, and CSS placement. The action goes through listingFor/permit, matches the finding by start, end and text so a stale page gets 409, reuses edit (re-check, audit, approval cleared, live listing.changed emit), and no contract cell is loosened. No new dependency. Non-blocking: useSuggestion import in src/mcp.ts is out of alphabetical order.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs