From 0ae09615dd237459199e1b0ab5f52bda3416833b Mon Sep 17 00:00:00 2001 From: davd-gzl <60177543+davd-gzl@users.noreply.github.com> Date: Thu, 1 Oct 2026 03:27:05 +0900 Subject: [PATCH 1/3] A11y: title every control on the map, places and add-place screens The control-title guard still allowlisted App.tsx, the map, visits and the place picker pending a branch that has since merged, so 49 controls there carried no tooltip. Each now takes its visible label or aria-label as its title, the Trips layer and the gallery's Add button keep one when their conditional title fell through to undefined, and the allowlist is gone. --- apps/postcards/src/app/App.tsx | 2 +- apps/postcards/src/features/map/MapScreen.tsx | 14 ++++++- apps/postcards/src/features/map/MapView.tsx | 1 + .../src/features/travel/PlacePicker.tsx | 3 ++ .../src/features/visits/AddPlaceForm.tsx | 15 ++++++- .../src/features/visits/PhotoGallery.tsx | 20 +++++++++- .../src/features/visits/PlaceSearch.tsx | 11 +++--- .../src/features/visits/PlacesScreen.tsx | 39 ++++++++++++++++--- .../src/features/visits/StateToggles.tsx | 10 ++--- .../tests/unit/controlTitles.spec.ts | 13 +------ 10 files changed, 93 insertions(+), 35 deletions(-) diff --git a/apps/postcards/src/app/App.tsx b/apps/postcards/src/app/App.tsx index 4d0cfe7..4333f15 100644 --- a/apps/postcards/src/app/App.tsx +++ b/apps/postcards/src/app/App.tsx @@ -345,7 +345,7 @@ export function App() { return (
{t("places.noMatch")}{" "}
{q && (
-
{t("places.browse.emptyStatus")}{" "}
{status !== "all" && (
-
{results.map((r, i) => {
@@ -221,49 +225,48 @@ export function PlaceSearch({
? t("states.removeFromVisited", { name: r.place.name })
: t("places.row.markVisitedAria", { name: r.place.name });
return (
-
+
{results.map((r, i) => (
+ // The option itself takes the click: an option may hold no control,
+ // so a button inside it would be hidden from a screen reader.
diff --git a/apps/postcards/src/styles.css b/apps/postcards/src/styles.css
index 5664673..2c36d58 100644
--- a/apps/postcards/src/styles.css
+++ b/apps/postcards/src/styles.css
@@ -704,7 +704,8 @@ h1.brand {
overflow-y: auto;
}
.results li button,
-.results div button {
+.results div button,
+.results li[role="option"] {
width: 100%;
display: flex;
align-items: center;
@@ -716,16 +717,18 @@ h1.brand {
border-radius: var(--radius-sm);
text-align: left;
color: var(--text);
+ cursor: pointer;
}
.results li button:hover,
.results li button:focus-visible,
.results div button:hover,
-.results div button:focus-visible {
+.results div button:focus-visible,
+.results li[role="option"]:hover {
background: var(--bg-sub);
}
/* The global search splits each row into TWO cells โ the first shows the
place, the trailing chip logs it โ so browsing can never accidentally mark
- something visited. (The Trips PlacePicker keeps single-button rows.) */
+ something visited. (The Trips PlacePicker keeps one-action rows.) */
.results-split [role="row"] {
display: flex;
align-items: center;
@@ -2432,6 +2435,7 @@ a.btn-ghost {
/* Active (keyboard) search option (never the trailing Add chip) */
.results li button.opt-active,
.results div button.opt-active,
+.results li.opt-active,
.results li[aria-selected="true"] button:not(.result-add) {
background: var(--bg-sub);
outline: 2px solid var(--accent);
diff --git a/apps/postcards/tests/e2e/a11y.spec.ts b/apps/postcards/tests/e2e/a11y.spec.ts
index e3853c5..9325bff 100644
--- a/apps/postcards/tests/e2e/a11y.spec.ts
+++ b/apps/postcards/tests/e2e/a11y.spec.ts
@@ -154,6 +154,16 @@ const SCREENS: [string, (page: Page) => Promise