From ed9a8e2c593aa6c46bde23b3bdf75526cd656c78 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:12:07 -0300 Subject: [PATCH 1/6] docs: design compact job detail actions --- ...09-23-compact-job-detail-actions-design.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md diff --git a/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md b/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md new file mode 100644 index 0000000..dd0b8d4 --- /dev/null +++ b/docs/superpowers/specs/2026-09-23-compact-job-detail-actions-design.md @@ -0,0 +1,103 @@ +# Compact Job Detail Actions + +## Context + +The job-detail screen currently presents opening the original listing, sharing, +saving, and reporting as four full-width buttons stacked inside its fixed action +dock. The layout makes every action appear equally important and consumes a large +part of short mobile viewports. The action dock must remain safe-area aware and +keep the final scroll content reachable, but it should no longer obscure so much +of the listing. + +## Product decision + +Keep opening the original listing as the only persistent primary action. Replace +the three stacked secondary buttons with one compact `Actions` trigger. The +trigger opens a native bottom sheet containing sharing, saving, and reporting as +separate rows. + +This preserves immediate access to the authoritative source while letting the +user deliberately choose a secondary action without presenting four nested or +stacked buttons. Opening the original listing remains one tap; secondary actions +require two taps. + +## Dock layout + +The fixed dock contains one horizontal row above the bottom safe area: + +- a flexible mint primary button for opening the original listing; +- a bounded secondary `Actions` button with an ellipsis icon; +- 44-point minimum interactive height for both controls; +- the existing paper surface and top hairline boundary; +- compact horizontal and vertical spacing from the shared NativeWind scale. + +The dock continues to report its measured total height, including safe-area +padding. The job-detail `ScrollView` continues to use that measurement for its +bottom content inset, so the final source, tag, or similar listing remains +reachable above the dock. + +## Secondary action sheet + +The `Actions` trigger opens a React Native modal presented as a native page sheet. +The sheet owns a safe-area-aware paper surface, a title, an explicit close control, +and three independent rows: + +1. share the job; +2. save the job, or remove it from saved jobs when already selected; +3. report a problem. + +Each row combines the existing Lucide icon, localized label, and a minimum +52-point row height. The saved row exposes its selected state to assistive +technology and uses both text and filled icon state, rather than color alone. +Reporting keeps muted visual emphasis but remains a normal accessible action, not +a destructive confirmation. + +Selecting an action closes the sheet before invoking its existing callback. The +sheet also closes through its close control, Android back behavior, and the native +modal dismissal path. Reopening it reflects the current saved state and label. + +## Component boundaries + +`JobDetailActions` remains the reusable owner of the dock, sheet visibility, and +presentation. Its callbacks remain narrow and unchanged: the job-details screen +continues to own external URL validation, native sharing, device-local saved +state, and analytics. + +The component receives one additional localized `actions` label and one localized +sheet-close label. No service, context, navigation, or persistence boundary +changes. No new dependency is required. + +## Localization and accessibility + +Add the new visible labels to the typed job message contract and all six supported +catalogs. Existing share, save/unsave, and report strings are reused. The action +sheet title and close control receive explicit localized accessibility labels. + +The implementation preserves Dynamic Type, light and dark themes, logical reading +order, 44-point targets, semantic button roles, selected state, bottom safe area, +and Android hardware-back dismissal. Long translations may wrap inside sheet rows +without reducing the touch target. + +## Testing + +Behavioral component tests will first describe the new contract: + +- only the primary action and `Actions` trigger are visible in the closed dock; +- secondary actions are absent until the sheet opens; +- opening and closing the sheet works through visible controls and request-close; +- each secondary row closes the sheet and calls only its matching callback; +- saved and unsaved states expose the correct label and accessibility state; +- dock layout measurement continues to report the compact measured height. + +The job-detail screen test will verify that the new localized trigger opens the +sheet and that the existing source and sharing behaviors remain connected. The +complete repository check must pass after the implementation. This is a +JavaScript presentation change and does not require regenerating native projects. + +## Out of scope + +- changing analytics event contracts; +- changing external URL, sharing, saving, or reporting behavior; +- adding swipe gestures or custom animation systems; +- redesigning the job-detail content or header; +- promoting the compact action pattern to unrelated screens. From be13b0941947665284f4f2c931623c47319565a3 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:14:40 -0300 Subject: [PATCH 2/6] docs: plan compact job detail actions --- ...mpact-job-detail-actions-implementation.md | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md diff --git a/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md b/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md new file mode 100644 index 0000000..631d9ec --- /dev/null +++ b/docs/superpowers/plans/2026-09-23-compact-job-detail-actions-implementation.md @@ -0,0 +1,243 @@ +# Compact Job Detail Actions Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the four-button job-detail footer with a compact primary action and a native secondary-action sheet. + +**Architecture:** `JobDetailActions` continues to own dock presentation and height measurement while adding local modal visibility. `JobDetailsScreen` keeps all product callbacks and receives no new behavioral responsibility; typed localization supplies the two new labels across all six catalogs. + +**Tech Stack:** React Native 0.86, Expo SDK 57, TypeScript, NativeWind, React Native Testing Library, Jest, Lucide React Native. + +--- + +## File map + +- Modify `src/components/job-detail-actions/index.tsx`: render the compact dock and own the secondary-action modal. +- Modify `src/app/jobs/details/index.tsx`: pass the new localized action and close labels. +- Modify `src/i18n/types.ts`: extend the typed jobs message contract. +- Modify `src/i18n/messages.ts`: add translations for `actions` and `closeActions` in all six locale extensions. +- Modify `tests/components/job-detail-actions.test.tsx`: define compact dock, modal, callback, saved-state, dismissal, and measurement behavior. +- Modify `tests/app/job-details.test.tsx`: verify the screen opens the action sheet before sharing. +- Modify `tests/i18n/messages.test.ts`: require both new labels in every locale. +- Modify `.knowledge/design_system/foundations_and_components.md`: replace the obsolete four-independent-buttons contract. +- Modify `.knowledge/patterns/navigation.md`: document the reduced measured action dock. + +### Task 1: Lock the localized contract + +**Files:** +- Modify: `tests/i18n/messages.test.ts` +- Modify: `src/i18n/types.ts` +- Modify: `src/i18n/messages.ts` + +- [ ] **Step 1: Write the failing locale assertions** + +Extend the test-only jobs shape with `actions?: string` and `closeActions?: string`, then assert both values for every supported locale: + +```ts +expect(copy?.jobs?.actions).toBeTruthy(); +expect(copy?.jobs?.closeActions).toBeTruthy(); +``` + +- [ ] **Step 2: Run the locale test and verify the red state** + +Run: + +```bash +npx jest tests/i18n/messages.test.ts --runInBand +``` + +Expected: FAIL because `jobs.actions` and `jobs.closeActions` are absent. + +- [ ] **Step 3: Add the typed keys and six translations** + +Add these fields beside the other job action labels in `src/i18n/types.ts`: + +```ts +actions: string; +closeActions: string; +``` + +Add locale-specific values to each native jobs extension in `src/i18n/messages.ts`: + +```ts +// en +actions: "Actions", +closeActions: "Close job actions", + +// pt-BR +actions: "Ações", +closeActions: "Fechar ações da vaga", + +// es +actions: "Acciones", +closeActions: "Cerrar acciones del empleo", + +// it +actions: "Azioni", +closeActions: "Chiudi azioni del lavoro", + +// fr +actions: "Actions", +closeActions: "Fermer les actions du poste", + +// de +actions: "Aktionen", +closeActions: "Stellenaktionen schließen", +``` + +- [ ] **Step 4: Run the locale test and verify green** + +Run the same Jest command. Expected: all localization tests PASS. + +- [ ] **Step 5: Commit the localization micro-change** + +```bash +git add src/i18n/types.ts src/i18n/messages.ts tests/i18n/messages.test.ts +git commit -m "feat(i18n): add compact job action labels" +``` + +### Task 2: Define the compact action behavior + +**Files:** +- Modify: `tests/components/job-detail-actions.test.tsx` +- Modify: `tests/app/job-details.test.tsx` + +- [ ] **Step 1: Replace the legacy component expectation with closed-dock behavior** + +Render the component with `actions: "Actions"` and `closeActions: "Close job actions"`. Assert that `Open original listing` and `Actions` are visible, while `Share job`, `Save job`, and `Report a problem` are absent before opening the sheet. + +Keep the layout event assertion, using a compact sample height: + +```ts +await fireEvent(screen.getByTestId("job-detail-actions"), "layout", { + nativeEvent: { layout: { height: 72, width: 390, x: 0, y: 0 } }, +}); +expect(onHeightChange).toHaveBeenCalledWith(72); +``` + +- [ ] **Step 2: Add modal interaction expectations** + +Open `Actions`, press each secondary row in a fresh render or reopen cycle, and verify only the corresponding callback fires. Assert the sheet disappears after every selection. Also fire `requestClose` on the modal test ID and press the localized close control. + +For saved state, rerender with `isSaved` and `save: "Remove saved job"`, then assert: + +```ts +expect(screen.getByLabelText("Remove saved job").props.accessibilityState).toEqual({ + selected: true, +}); +``` + +- [ ] **Step 3: Update the screen test to open the sheet before sharing** + +Change the direct share press to: + +```ts +await fireEvent.press(screen.getByText("Actions")); +await fireEvent.press(screen.getByText("Share job")); +expect(Share.share).toHaveBeenCalled(); +``` + +- [ ] **Step 4: Run both tests and verify the red state** + +```bash +npx jest tests/components/job-detail-actions.test.tsx tests/app/job-details.test.tsx --runInBand +``` + +Expected: FAIL because the dock still renders four stacked actions and has no action modal. + +### Task 3: Implement the compact dock and action sheet + +**Files:** +- Modify: `src/components/job-detail-actions/index.tsx` +- Modify: `src/app/jobs/details/index.tsx` + +- [ ] **Step 1: Extend the component labels and add local visibility** + +Add `actions` and `closeActions` to `JobDetailActionLabels`, import `MoreHorizontal`, `X`, `Modal`, and `useState`, then own the sheet state: + +```ts +const [actionsVisible, setActionsVisible] = useState(false); + +const runAction = (action: () => void) => { + setActionsVisible(false); + action(); +}; +``` + +- [ ] **Step 2: Replace the stacked buttons with one horizontal row** + +Inside the safe-area dock, render a compact row with the existing mint original-listing button using `flex-1`, plus a bordered `Actions` trigger using `MoreHorizontal`. Preserve 44-point minimum height and the existing measured `SafeAreaView`. + +- [ ] **Step 3: Render the native modal sheet** + +Render a `Modal` with `animationType="slide"`, `presentationStyle="pageSheet"`, `visible={actionsVisible}`, `onRequestClose={() => setActionsVisible(false)}`, and `testID="job-detail-action-sheet"`. Its safe-area content contains: + +```tsx + runAction(onShare)}>{/* Share2 + labels.share */} + runAction(onToggleSaved)} +>{/* Bookmark + labels.save */} + runAction(onReport)}>{/* CircleAlert + labels.report */} +``` + +Each row uses a minimum 52-point height. Add a header titled with `labels.actions` and an icon-only close control labelled with `labels.closeActions`. + +- [ ] **Step 4: Pass localized labels from the screen** + +Add these properties to the existing labels object in `src/app/jobs/details/index.tsx`: + +```ts +actions: messages.jobs.actions, +closeActions: messages.jobs.closeActions, +``` + +- [ ] **Step 5: Run the focused tests and verify green** + +Run the two-test Jest command from Task 2. Expected: both suites PASS. + +- [ ] **Step 6: Commit the behavior micro-change** + +```bash +git add src/components/job-detail-actions/index.tsx src/app/jobs/details/index.tsx tests/components/job-detail-actions.test.tsx tests/app/job-details.test.tsx +git commit -m "feat(ui): compact job detail actions" +``` + +### Task 4: Align project knowledge and verify the application + +**Files:** +- Modify: `.knowledge/design_system/foundations_and_components.md` +- Modify: `.knowledge/patterns/navigation.md` + +- [ ] **Step 1: Update the design-system contract** + +Replace the statement that the dock exposes four independent actions with a factual description of the primary original-listing button and the secondary native action sheet. + +- [ ] **Step 2: Update the navigation contract** + +Clarify that the measured dock is a compact single row and that its native sheet owns the secondary actions without changing scroll-inset behavior. + +- [ ] **Step 3: Run the complete repository check** + +```bash +npm run check +``` + +Expected: lint, typecheck, all Jest suites, and Expo Doctor PASS. If Expo Doctor alone fails because of local network or unavailable CocoaPods, rerun it with network access and report the native-tooling limitation separately; do not mask application failures. + +- [ ] **Step 4: Inspect the final diff and working tree** + +```bash +git diff --check +git status --short +``` + +Expected: no whitespace errors and only the planned knowledge files remain uncommitted. + +- [ ] **Step 5: Commit the knowledge micro-change** + +```bash +git add .knowledge/design_system/foundations_and_components.md .knowledge/patterns/navigation.md +git commit -m "docs: document compact job action dock" +``` + From 8eb8153311affce731fca22f91d9248a4116c836 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:15:57 -0300 Subject: [PATCH 3/6] feat(i18n): add compact job action labels --- src/i18n/messages.ts | 12 ++++++++++++ src/i18n/types.ts | 2 ++ tests/i18n/messages.test.ts | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/src/i18n/messages.ts b/src/i18n/messages.ts index 376c90f..beb182f 100644 --- a/src/i18n/messages.ts +++ b/src/i18n/messages.ts @@ -61,6 +61,8 @@ const webNativeMessages = { privacy: "Your preferences and visit history stay on this device.", title: "New matches for you", }, + actions: "Actions", + closeActions: "Close job actions", reportProblem: "Report a problem", }, }, @@ -97,6 +99,8 @@ const webNativeMessages = { privacy: "Suas preferências e seu histórico de visitas ficam somente neste dispositivo.", title: "Novas vagas para você", }, + actions: "Ações", + closeActions: "Fechar ações da vaga", reportProblem: "Reportar problema", }, }, @@ -133,6 +137,8 @@ const webNativeMessages = { privacy: "Tus preferencias y tu historial de visitas permanecen en este dispositivo.", title: "Nuevas coincidencias para ti", }, + actions: "Acciones", + closeActions: "Cerrar acciones del empleo", reportProblem: "Informar de un problema", }, }, @@ -169,6 +175,8 @@ const webNativeMessages = { privacy: "Le preferenze e la cronologia delle visite restano su questo dispositivo.", title: "Nuove corrispondenze per te", }, + actions: "Azioni", + closeActions: "Chiudi azioni del lavoro", reportProblem: "Segnala un problema", }, }, @@ -205,6 +213,8 @@ const webNativeMessages = { privacy: "Vos préférences et votre historique de visites restent sur cet appareil.", title: "De nouvelles correspondances pour vous", }, + actions: "Actions", + closeActions: "Fermer les actions du poste", reportProblem: "Signaler un problème", }, }, @@ -241,6 +251,8 @@ const webNativeMessages = { privacy: "Deine Einstellungen und dein Besuchsverlauf bleiben auf diesem Gerät.", title: "Neue Treffer für dich", }, + actions: "Aktionen", + closeActions: "Stellenaktionen schließen", reportProblem: "Problem melden", }, }, diff --git a/src/i18n/types.ts b/src/i18n/types.ts index e40a977..411db28 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -137,6 +137,8 @@ export interface FoundationMessages { noDescription: string; oldest: string; olderBadge: string; + actions: string; + closeActions: string; openOriginal: string; posted: string; regions: string; diff --git a/tests/i18n/messages.test.ts b/tests/i18n/messages.test.ts index ac7b355..07666f3 100644 --- a/tests/i18n/messages.test.ts +++ b/tests/i18n/messages.test.ts @@ -38,6 +38,8 @@ describe("localized messages", () => { fields?: { location?: string }; title?: string; }; + actions?: string; + closeActions?: string; newMatches?: { action?: string; title?: string }; reportProblem?: string; }; @@ -55,6 +57,8 @@ describe("localized messages", () => { expect(copy?.jobs?.newMatches?.action).toBeTruthy(); expect(copy?.jobs?.dataConfidence?.title).toBeTruthy(); expect(copy?.jobs?.dataConfidence?.fields?.location).toBeTruthy(); + expect(copy?.jobs?.actions).toBeTruthy(); + expect(copy?.jobs?.closeActions).toBeTruthy(); expect(copy?.jobs?.reportProblem).toBeTruthy(); } }); From 52b417c2331533f1be3e7fded02e5e32e6c1b6fc Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:20:43 -0300 Subject: [PATCH 4/6] feat(ui): compact job detail actions --- src/app/jobs/details/index.tsx | 2 + src/components/job-detail-actions/index.tsx | 172 +++++++++++++------ tests/app/job-details.test.tsx | 3 +- tests/components/job-detail-actions.test.tsx | 114 ++++++++---- 4 files changed, 207 insertions(+), 84 deletions(-) diff --git a/src/app/jobs/details/index.tsx b/src/app/jobs/details/index.tsx index 8f17562..c130c86 100644 --- a/src/app/jobs/details/index.tsx +++ b/src/app/jobs/details/index.tsx @@ -304,6 +304,8 @@ export function JobDetailsScreen({ id }: JobDetailsScreenProps): React.ReactNode setActionsVisible(false); const handleLayout = (event: LayoutChangeEvent) => { onHeightChange?.(event.nativeEvent.layout.height); }; + const runAction = (action: () => void) => { + closeActions(); + action(); + }; return ( - - - - - - {labels.openOriginal} - - - - - - {labels.share} - - - - - - {labels.save} - - - - - - {labels.report} - - - - + <> + + + + + + {labels.openOriginal} + + + setActionsVisible(true)} + > + + + {labels.actions} + + + + + + + + + + + + {labels.actions} + + + + + + + runAction(onShare)} + > + + + {labels.share} + + + runAction(onToggleSaved)} + > + + + {labels.save} + + + runAction(onReport)} + > + + + {labels.report} + + + + + + + ); } diff --git a/tests/app/job-details.test.tsx b/tests/app/job-details.test.tsx index 64cd8bb..ccb8e8f 100644 --- a/tests/app/job-details.test.tsx +++ b/tests/app/job-details.test.tsx @@ -27,7 +27,7 @@ describe("JobDetailsScreen", () => { expect(screen.getByText("React Native")).toBeTruthy(); expect(screen.getByText("Data confidence")).toBeTruthy(); expect(screen.getAllByText("Declared in source")).toHaveLength(2); - expect(screen.getByText("Report a problem")).toBeTruthy(); + expect(screen.queryByText("Report a problem")).toBeNull(); await waitFor(() => expect(markViewed).toHaveBeenCalledWith("gh_123")); await fireEvent.press(screen.getByLabelText("Show jobs from Openings")); expect(mockPush).toHaveBeenLastCalledWith("/communities/openings-dev/jobs"); @@ -35,6 +35,7 @@ describe("JobDetailsScreen", () => { expect(mockPush).toHaveBeenLastCalledWith("/authors/alice"); await fireEvent.press(screen.getByText("Open original listing")); expect(open).toHaveBeenCalledWith("https://github.com/openings-dev/jobs/issues/gh_123"); + await fireEvent.press(screen.getByText("Actions")); await fireEvent.press(screen.getByText("Share job")); expect(Share.share).toHaveBeenCalled(); }); diff --git a/tests/components/job-detail-actions.test.tsx b/tests/components/job-detail-actions.test.tsx index 85a5afe..f7b8f20 100644 --- a/tests/components/job-detail-actions.test.tsx +++ b/tests/components/job-detail-actions.test.tsx @@ -9,6 +9,37 @@ jest.mock("@react-native-async-storage/async-storage", () => ({ setItem: jest.fn(), })); +const LABELS = { + actions: "Actions", + closeActions: "Close job actions", + openOriginal: "Open original listing", + report: "Report a problem", + save: "Save job", + share: "Share job", +}; + +async function renderActions(isSaved = false) { + const actions = { + onOpenOriginal: jest.fn(), + onReport: jest.fn(), + onShare: jest.fn(), + onToggleSaved: jest.fn(), + }; + const onHeightChange = jest.fn(); + const screen = await render( + + + , + ); + + return { actions, onHeightChange, screen }; +} + describe("JobDetailActions", () => { beforeEach(() => { jest.clearAllMocks(); @@ -16,43 +47,62 @@ describe("JobDetailActions", () => { jest.mocked(AsyncStorage.setItem).mockResolvedValue(); }); - it("exposes four independent actions and reports its dock height", async () => { - const onHeightChange = jest.fn(); - const actions = { - onOpenOriginal: jest.fn(), - onReport: jest.fn(), - onShare: jest.fn(), - onToggleSaved: jest.fn(), - }; - const screen = await render( - - - , - ); + it("keeps only the primary action and action chooser in the compact dock", async () => { + const { actions, onHeightChange, screen } = await renderActions(); + + expect(screen.getByText("Open original listing")).toBeTruthy(); + expect(screen.getByText("Actions")).toBeTruthy(); + expect(screen.queryByText("Share job")).toBeNull(); + expect(screen.queryByText("Save job")).toBeNull(); + expect(screen.queryByText("Report a problem")).toBeNull(); await fireEvent(screen.getByTestId("job-detail-actions"), "layout", { - nativeEvent: { layout: { height: 220, width: 390, x: 0, y: 0 } }, + nativeEvent: { layout: { height: 72, width: 390, x: 0, y: 0 } }, }); - expect(onHeightChange).toHaveBeenCalledWith(220); - await fireEvent.press(screen.getByText("Open original listing")); - await fireEvent.press(screen.getByText("Share job")); - await fireEvent.press(screen.getByText("Save job")); - await fireEvent.press(screen.getByText("Report a problem")); + expect(onHeightChange).toHaveBeenCalledWith(72); expect(actions.onOpenOriginal).toHaveBeenCalledTimes(1); - expect(actions.onShare).toHaveBeenCalledTimes(1); - expect(actions.onToggleSaved).toHaveBeenCalledTimes(1); - expect(actions.onReport).toHaveBeenCalledTimes(1); + }); + + it("opens and dismisses the secondary action sheet", async () => { + const { screen } = await renderActions(); + + await fireEvent.press(screen.getByText("Actions")); + + expect(screen.getByText("Share job")).toBeTruthy(); + expect(screen.getByText("Save job")).toBeTruthy(); + expect(screen.getByText("Report a problem")).toBeTruthy(); + + await fireEvent.press(screen.getByLabelText("Close job actions")); + expect(screen.queryByText("Share job")).toBeNull(); + + await fireEvent.press(screen.getByText("Actions")); + await fireEvent(screen.getByTestId("job-detail-action-sheet"), "requestClose"); + expect(screen.queryByText("Share job")).toBeNull(); + }); + + it.each([ + ["Share job", "onShare"], + ["Save job", "onToggleSaved"], + ["Report a problem", "onReport"], + ] as const)("closes the sheet after choosing %s", async (label, callback) => { + const { actions, screen } = await renderActions(); + + await fireEvent.press(screen.getByText("Actions")); + await fireEvent.press(screen.getByText(label)); + + expect(actions[callback]).toHaveBeenCalledTimes(1); + expect(screen.queryByText("Share job")).toBeNull(); + }); + + it("exposes the saved action as selected", async () => { + const { screen } = await renderActions(true); + + await fireEvent.press(screen.getByText("Actions")); + + expect(screen.getByLabelText("Remove saved job").props.accessibilityState).toEqual({ + selected: true, + }); }); }); From 28eacb2119122f3c22f067bcfc4331b686656bf9 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:23:09 -0300 Subject: [PATCH 5/6] fix(ui): use approved compact spacing --- src/components/job-detail-actions/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/job-detail-actions/index.tsx b/src/components/job-detail-actions/index.tsx index b60971c..5b5c1f4 100644 --- a/src/components/job-detail-actions/index.tsx +++ b/src/components/job-detail-actions/index.tsx @@ -63,7 +63,7 @@ export function JobDetailActions({ @@ -73,7 +73,7 @@ export function JobDetailActions({ setActionsVisible(true)} > From 8af325d9d2b8559d8f90924e4c1c046fd47479d5 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Wed, 23 Sep 2026 13:23:09 -0300 Subject: [PATCH 6/6] docs: document compact job action dock --- .../design_system/foundations_and_components.md | 14 ++++++++------ .knowledge/patterns/navigation.md | 8 +++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.knowledge/design_system/foundations_and_components.md b/.knowledge/design_system/foundations_and_components.md index bee6119..cfb28dd 100644 --- a/.knowledge/design_system/foundations_and_components.md +++ b/.knowledge/design_system/foundations_and_components.md @@ -85,12 +85,14 @@ result toolbar. Dismissal persists locally and the action applies the existing `newOnly` filter contract. Job details use a wordmark/X header, identity-first hierarchy, 32-point title, -`Data confidence` card, and a fixed safe-area action dock. The dock exposes opening -the original listing, sharing, saving, and reporting as four independent 44-point -actions. `Data confidence` shows verification availability, publication time, -source count, ordered provenance fields, and source links without inventing missing -facts. Community and author screens are identity-led destinations, not generic -search results with a label changed. +`Data confidence` card, and a fixed safe-area action dock. The compact dock keeps +opening the original listing as its persistent primary action and exposes sharing, +saving, and reporting through a native secondary-action sheet. Every dock control +and sheet row preserves the minimum interactive target. `Data confidence` shows +verification availability, publication time, source count, ordered provenance +fields, and source links without inventing missing facts. Community and author +screens are identity-led destinations, not generic search results with a label +changed. Lucide React Native is the application icon language. Use the icon that matches the responsive web surface, an explicit semantic color, and a consistent 1.8 stroke. diff --git a/.knowledge/patterns/navigation.md b/.knowledge/patterns/navigation.md index 4d8508f..2dab694 100644 --- a/.knowledge/patterns/navigation.md +++ b/.knowledge/patterns/navigation.md @@ -41,9 +41,11 @@ modal dismissal, and focus restoration. A full-screen detail experience may requ a different native presentation from the web dialog while preserving the same information hierarchy and source-of-truth action. -The job-detail action dock lives outside its `ScrollView`, reports its measured -height, and adds that height to the scroll content inset. This keeps the last source, -tag, or similar job reachable above the bottom safe area. +The job-detail action dock lives outside its `ScrollView` as a compact single row, +reports its measured height, and adds that height to the scroll content inset. Its +native action sheet owns sharing, saving, and reporting without increasing the +persistent dock height. This keeps the last source, tag, or similar job reachable +above the bottom safe area. Deep links and outbound GitHub links are security boundaries. Internal routes encode stable IDs, repositories, and handles. External actions accept deliberate HTTPS