-
Notifications
You must be signed in to change notification settings - Fork 0
fix(jobs): restore builder identity on intro requests and route them through the one Bedrock bot #258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kwame-kka
wants to merge
9
commits into
main
Choose a base branch
from
claude/bedrock-intro-flow-review-bu07xt
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
fix(jobs): restore builder identity on intro requests and route them through the one Bedrock bot #258
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
28edc4f
docs(jobs): plan for builder intro requests losing builder identity
claude 05210ca
docs(jobs): resolve open questions on the builder intro request plan
claude 2fb0369
fix(jobs): restore builder identity on intro requests and route them …
claude 651094f
fix(jobs): read Sputnik intro timestamps as UTC
claude 479b9e9
fix(jobs): make the builder/jobs-team tag colours actually distinguis…
claude 2dede9e
chore(jobs): add a no-database local preview for the intro-requests zone
claude 7401eca
chore(jobs): fail the intro preview legibly on Python 3.9
claude ebf1159
chore(jobs): Node-only stub for the intro-requests preview
claude 4f0b8dd
fix(jobs): stop the preview stub blanking the dashboard
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
financial_forecasting/frontend-v2/scripts/preview-intro-api.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| /** | ||
| * Zero-dependency stub API for previewing the Jobs > Intro requests zone. | ||
| * | ||
| * Node-only alternative to scripts/preview_intro_requests.py, for machines | ||
| * without Python 3.10+. You need Node anyway to run the frontend, so this | ||
| * makes the preview a one-toolchain job. | ||
| * | ||
| * The payloads below are recorded verbatim from the real | ||
| * routes/jobs_intro.py router (that is what the Python version serves live); | ||
| * only the timestamps are recomputed so the relative dates stay sensible. | ||
| * | ||
| * MODE=after (default) builder lookup succeeds | ||
| * MODE=before builder lookup returns nothing, as public.users does for | ||
| * bedrock_user under RLS. On this branch the fallback chain | ||
| * catches it and renders "Builder #428"; on main the same | ||
| * empty lookup rendered a bare "from — (builder)". | ||
| * | ||
| * Usage, from financial_forecasting/frontend-v2, in two terminals: | ||
| * | ||
| * node scripts/preview-intro-api.mjs | ||
| * npm run dev | ||
| * | ||
| * Then open http://localhost:4200/jobs | ||
| */ | ||
| import { createServer } from 'node:http'; | ||
|
|
||
| const MODE = process.env.MODE === 'before' ? 'before' : 'after'; | ||
| const PORT = 8000; | ||
|
|
||
| const daysAgo = (n) => new Date(Date.now() - n * 86_400_000).toISOString(); | ||
|
|
||
| const staffRow = { | ||
| id: '239f08c7-4e45-4539-8c41-2302bb35de67', | ||
| source: 'staff', | ||
| contact_id: 1001, | ||
| contact_name: 'Dana Whitfield', | ||
| contact_company: 'Northwind', | ||
| contact_title: 'Engineering Manager', | ||
| connector_staff_id: 4, | ||
| connector_name: 'Sam Okafor', | ||
| connector_email: 'sam.okafor@example.org', | ||
| builder_id: null, | ||
| builder_cohort: null, | ||
| requested_by: 'jordan.reyes@example.org', | ||
| requested_by_name: 'Jordan Reyes', | ||
| specific_ask: 'industry_advice', | ||
| context: | ||
| 'Sample staff→staff ask. Would you be open to introducing one of our ' + | ||
| 'builders for a 20-minute coffee chat?', | ||
| status: 'pending', | ||
| response_note: null, | ||
| responded_at: null, | ||
| created_at: daysAgo(4), | ||
| }; | ||
|
|
||
| const builderRow = { | ||
| id: '15', | ||
| source: 'builder', | ||
| contact_id: 1002, | ||
| contact_name: 'Priya Raman', | ||
| contact_company: 'Lumen Labs', | ||
| contact_title: 'Director of Product Engineering', | ||
| connector_staff_id: 4, | ||
| connector_name: null, | ||
| connector_email: 'sam.okafor@example.org', | ||
| builder_id: 428, | ||
| builder_cohort: MODE === 'after' ? 'March 2026 L1+' : null, | ||
| requested_by: MODE === 'after' ? 'alex.mensah@example.org' : 'Builder #428', | ||
| requested_by_name: MODE === 'after' ? 'Alex Mensah' : 'Builder #428', | ||
| specific_ask: 'industry_advice', | ||
| context: | ||
| 'Sample builder ask. Their background bridging product strategy and ' + | ||
| 'technical architecture is exactly the path I am trying to grow into.', | ||
| status: 'pending', | ||
| response_note: null, | ||
| responded_at: null, | ||
| created_at: daysAgo(38), | ||
| }; | ||
|
|
||
| const ME = { | ||
| email: 'sam.okafor@example.org', | ||
| name: 'Sam Okafor', | ||
| sub: 'sam', | ||
| salesforce_connected: false, | ||
| google_connected: true, | ||
| slack_configured: true, | ||
| }; | ||
|
|
||
| const send = (res, body) => { | ||
| const json = JSON.stringify(body); | ||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/json', | ||
| 'Content-Length': Buffer.byteLength(json), | ||
| }); | ||
| res.end(json); | ||
| }; | ||
|
|
||
| createServer((req, res) => { | ||
| const path = (req.url || '').split('?')[0]; | ||
|
|
||
| if (path === '/auth/me') return send(res, ME); | ||
| if (path === '/api/jobs/intro-requests') { | ||
| return send(res, { success: true, data: [staffRow, builderRow] }); | ||
| } | ||
|
|
||
| // Response shape matters: hooks destructure these differently, and handing | ||
| // back the wrong one throws inside render (a caught .filter on an object | ||
| // blanks the whole page). Salesforce endpoints return bare arrays; the | ||
| // notification badge reads data.data.count; the rest use {success, data}. | ||
| if (path.startsWith('/api/salesforce/')) return send(res, []); | ||
| if (path === '/api/notifications/unread-count') { | ||
| return send(res, { success: true, data: { count: 0 } }); | ||
| } | ||
| // Every other zone renders from an empty result. | ||
| return send(res, { success: true, data: [] }); | ||
| }).listen(PORT, '127.0.0.1', () => { | ||
| console.log(`\n intro-requests preview API — MODE=${MODE}`); | ||
| console.log(` listening on http://127.0.0.1:${PORT}`); | ||
| console.log(' now run "npm run dev" in another terminal, then open'); | ||
| console.log(' http://localhost:4200/jobs\n'); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Accepted builder intro asks disappear from the default inbox before the new 'Mark intro made' action can be used, because the builder-row filter (
open_builder) only keepsstatus = 'pending', unlike the staff-row filter (open_staff) which also keeps'accepted'.Extended reasoning...
routes/jobs_intro.pybuilds two different "still open" filters for the two sources that feed the intro-requests inbox. For staff→staff asks (bedrock.intro_request), line ~176 defines:For Sputnik builder asks (
public.intro_requests), a few lines later:open_staffdeliberately keeps'accepted'rows visible in the default (non-closed) view, because an accepted-but-not-yet-completed ask is still an actionable, open item — the connector still has to make the intro.open_builderonly kept'pending'— an asymmetry that was harmless before this PR because a builder ask could never be marked complete anyway (see below).This PR changes that. It fixes
BUILDER_STATUS_MAPsocompletedmaps tocompletedinstead of collapsing intoapproved(previously bothacceptedandcompletedmapped toapproved, losing the distinction), and it removes ther.source === "staff"gate on the "Mark intro made" button inJobsHome.tsx, usingisAccepted = r.status === "accepted" || r.status === "approved"to decide when to show it. The stated intent (in the PR description) is explicit: "builder asks can now be marked intro-made."But the moment a staff member accepts a builder ask,
respond_intro_requestmapsaccepted→BUILDER_STATUS_MAP["accepted"] = "approved"and writesstatus='approved'intopublic.intro_requests. On the very next refetch of the default inbox (useIntroRequests('all', showClosed=false)→include_closed=false→ theopen_builderclause), that row no longer matchesir.status = 'pending', so it's excluded from the response entirely. The card disappears from the staff member's "For you" list — along with the newly-enabled "Mark intro made" button that would let them close the loop.The only way to reach the button again is to toggle "show closed", but that flips
include_closed=trueand pulls in every declined/completed ask too, defeating the purpose of a scoped default view. So the feature this PR set out to ship (staff can accept a builder's ask and later mark the intro made) is unreachable along its intended path — a staff member who accepts an ask immediately loses the ability to act on it from the default screen, and has to know to dig through the closed list to find it again.Step-by-step proof:
status = 'pending'inpublic.intro_requests. It's fetched byopen_builder's'pending'check and shows up under "For you".PATCH /api/jobs/intro-requests/{id}withstatus: 'accepted', source: 'builder'.respond_intro_requestlooks upBUILDER_STATUS_MAP['accepted']→'approved', and runsUPDATE public.intro_requests SET status='approved' ....["jobs", "intro-requests"]and refetches with the samebox='all', include_closed=falseparams used byIntroRequestsZone.open_builder = " AND ir.status = 'pending'"; the row's status is now'approved', so it fails this predicate and is dropped fromdata.isAcceptedincludes'approved'— never gets a chance to render, because the row isn't in the payload at all.This is a real functional gap introduced by this PR's own feature (it didn't exist before because builder asks couldn't be completed at all, so there was nothing to lose visibility of). The fix is small and mirrors the existing staff-side pattern: include
'approved'alongside'pending'inopen_builder, e.g." AND ir.status IN ('pending','approved')", so an accepted-but-not-completed builder ask stays visible in the default view exactly like an accepted staff ask does.