Skip to content

Release screenshot 03-hiring-home-overview.png predates the Average Days to Offer tile - #76

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-66-reshoot-hiring-overview
Sep 7, 2026
Merged

Release screenshot 03-hiring-home-overview.png predates the Average Days to Offer tile#76
os-sam merged 1 commit into
mainfrom
claude/issue-66-reshoot-hiring-overview

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #66.

docs/screenshots/03-hiring-home-overview.png and its caption predated the fourth tile #55 adds to
ats_employer_hiring. #55 has since merged (PR #68), so the shot was stale in fact, not prospectively.
Docs only — no file under src/ is touched.

Old and new, side by side

Before (e759ba5) After (this branch)
before after

Both files live at the same path; the left link pins the base commit so the replaced image is still reachable.

The visible difference. A fourth KPI card, Average Days to Offer — 30.0 · Avg Days to Offer, in green
(colorVariant: 'success'), on a new second tile row at layout: { x: 0, y: 2 }. The dashboard's own
subtitle gains a clause: "…interviews this week, average days to offer and the pipeline by stage."
Because the tile opens that second row, Pipeline by Stage moves from y: 2 to y: 4 and its bar chart
now begins below the 900 px fold — in the old shot the whole chart fitted; in the new one the header and the
top of the plot area show. The three original tiles, the sidebar and the chrome are otherwise unchanged.

That crop is the set's existing shape, not a regression introduced here: 01-platform-home-overview.png has
always had two tile rows with its "Applications per Week" chart cut off at exactly the same place. Matching
01 is why the shot stays a 1440 × 900 viewport capture instead of becoming a full-page one — all seven
files are 1440 × 900, and a taller 03 would be the only shot in the set that is not.

How the shot was taken

The recipe docs/screenshots/README.md documents, unchanged:

OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai npx objectstack dev --fresh --database-driver memory -p 4631 --log-level info

The set was shot at 809 rows; this one is at 818. The README's preamble already explained that gap (#53 added
one accepted ats_offer per hired application) and now records that 03 is the one shot re-taken on the
far side of it.

The caption numbers, each re-measured

Every number was measured on this boot through POST /api/v1/analytics/dataset/query — the same datasets the
widgets bind to — as admin@quillstone.example, so the row-level scope in the compiled statement is the one
the dashboard actually applies. None was carried over from the old caption.

The new tile: 30.0

The filter key the analytics service reads is selection.runtimeFilter, and only that one:

SELECT AVG(days_to_offer) AS "avg_days_to_offer" FROM "ats_application"
WHERE stage = $1 AND ("ats_application"."employer_org" IN ($2))
-> [{"avg_days_to_offer": 30}]

The widget filter (stage = $1) and the row-level read scope (employer_org IN ($2)) are both visible in the
statement, which is what makes this the right number rather than a plausible one.

The four wrong spellings were run against the same boot to confirm they do not fail loudly. Each returns
HTTP 200 and compiles a statement with no stage predicate at all:

Where the filter was written HTTP Compiled WHERE Answer
selection.runtimeFilter 200 stage = $1 AND (employer_org IN ($2)) 30
body.filter 200 (employer_org IN ($1)) 30.333333333333332
selection.filter 200 (employer_org IN ($1)) 30.333333333333332
selection.filters 200 (employer_org IN ($1)) 30.333333333333332
selection.where 200 (employer_org IN ($1)) 30.333333333333332

30.3 would have looked entirely reasonable in a caption. It is the average over all 27 of Quillstone's
applications that carry a days_to_offer, not over the hired ones the tile is scoped to.

The number in the caption is the number in the image. The tile renders 30.0, because
ats_application_metrics.avg_days_to_offer declares format: '0.0'; the query returns 30. The caption says
30.0, read off the rendered page (document.body.innerText of the captured page contains
Average Days to Offer … 30.0 … Avg Days to Offer), and the query agrees with it.

The four carried-over numbers — all still correct

Caption number Compiled statement Result
3 open jobs SELECT COUNT(*) FROM "ats_job" WHERE status = $1 AND (employer_org IN ($2)) 3
18 awaiting action SELECT COUNT(*) FROM "ats_application" WHERE stage IN ($1, $2) AND (employer_org IN ($3)) 18
6 interviews this week SELECT COUNT(*) FROM "ats_interview" WHERE ((status IS NULL OR status != $1) AND scheduled_at >= $2 AND scheduled_at < $3) AND (application IN ($4 … $30)) 6
pipeline 10 / 8 / 5 / 2 / 1 (+1 rejected) SELECT stage, COUNT(*) FROM "ats_application" WHERE (employer_org IN ($1)) GROUP BY stage Applied 10 · Screening 8 · Interview 5 · Offer 2 · Hired 1 · Rejected 1

Nothing moved, which is the expected result: #55 added a measure, it changed no stage counts. The first three
are also legible in the image itself and match.

Whole-set pass

All seven shots were re-taken against this boot with the same recipe and compared to the committed files.
Method per shot, not a bare "checked":

Shot Verdict How established
01-platform-home-overview.png accurate Re-shot as admin@platform.example. Renders 9 / 22 / 30 / 70, review queue 2 / 6, same weekly bars — identical to the committed image.
02-platform-hiring-funnel.png accurate Re-shot as admin@platform.example: 15 rejected · 0 withdrawn · 185 in pipeline, unchanged. The funnel's hover-only values re-measured through the dataset query (GROUP BY stage, no employer_org scope for platform staff): 88 / 46 / 28 / 14 / 9, and 88+46+28+14+9 = 185 confirms the third tile.
03-hiring-home-overview.png stale — reshot here The Average Days to Offer tile was missing from image and caption. Reshot; caption extended; all five numbers re-measured above.
04-hiring-pipeline-kanban.png accurate Re-shot as admin@quillstone.example. 27 records; Applied 10 · Screening 8 · Interview 5 · Offer. Card face and every applied_at date match the committed image exactly (9/5/2026, 8/30/2026, 8/29/2026, 8/25/2026, 8/23/2026 …) — an independent check that #65 moved no date this shot renders. Only difference is a cursor hover highlight on one card.
05-hiring-interview-calendar.png accurate in substance Re-shot as admin@quillstone.example: 10 records, September 2026, R1 rounds on the 8th–12th and R2 on the 13th–17th, all inside the next two weeks — identical grid. Two chrome differences, neither caused by this card's merges: see the note below.
06-seeker-home-find-jobs.png accurate in substance Re-shot as candidate01@mail.example. 22 records; columns Job Title · Employer · City · Work Mode · Salary (min) · Salary (max) · Employment Type — unchanged. Same chrome note.
07-hiring-pipeline-kanban-zh-CN.png accurate Re-shot with locale: 'zh-CN': 已投递 10 · 筛选中 8 · 面试中 5 · OFFER, 27 条记录, same translated nav and column labels — identical to the committed image.

Does any other shot frame the Hiring Overview dashboard? No. Reading src/apps/*.nav.ts, exactly one entry
mounts ats_employer_hiring (nav_hiring_overview), and 03 is the only shot on it. The other two dashboard
shots are ats_platform_overview and ats_hiring_funnel.

Ripple 1 — #65, ats_application.last_activity_at

Does not reach the set. last_activity_at is rendered by exactly one surface: the seeker's mine
timeline view (src/views/application.view.ts, columns: ['display_name', 'job', 'stage', 'applied_at', 'last_activity_at']). A repo-wide grep finds no other view, kanban card list, calendar field or dataset
dimension naming it. No shot frames My Applications — 06 is Find Jobs, and the seeker persona appears in
that one shot only.

Checked rather than assumed on the two shots that could plausibly have carried a date: the kanban's card
fields are ['display_name', 'job', 'rating', 'applied_at'] and the calendar anchors on
ats_interview.scheduled_at. Both re-shot and both byte-comparable to the committed images, applied_at
values included. #74's diff touches only src/hooks/stamp.hook.ts, and only the last_activity_at
assignment inside it — applied_at and days_to_offer are untouched.

Ripple 2 — #33, highlightFields on ats_employer / ats_job / ats_candidate

Does not reach the set. highlightFields drives surfaces with no authored column list. The derived
surfaces in this app are the three filters slices — nav_platform_employers_pending,
nav_platform_jobs_pending (both src/apps/platform.nav.ts) and nav_seeker_my_profile
(src/apps/seeker.nav.ts) — plus record headers and lookup previews. None of the seven shots is any of
them.
01's sidebar lists "Employers Pending" and "Jobs Pending" as nav labels, but photographs neither
queue's contents.

Find Jobs specifically, which was the one to check hardest. It is an authored view, not a filters
slice
: nav_seeker_find_jobs is { type: 'object', objectName: 'ats_job', viewName: 'published' }, and
JobViews.listViews.published carries an explicit columns: seekerColumns list. So ats_job's new
highlightFields (title, employer, status, city, employment_type, work_mode) cannot reach it — and does
not: the live re-shot renders Job Title · Employer · City · Work Mode · Salary (min) · Salary (max) ·
Employment Type, exactly seekerColumns and exactly the committed image. Had the slice path been in play,
status would have appeared and both salary columns would have gone.

One thing the pass turned up — filed, not fixed here

05 and 06 show a breadcrumb leaf one word shorter than a boot today renders: … / Calendar vs
… / Interview Calendar, and … / Published vs … / Published Jobs. Cause established with git log -S:
both _views label keys landed in src/translations/en.ts in commit 0ad34d7 (#62) — the same commit that
added the screenshots — so those two shots came from a boot whose bundle did not yet carry them and the
breadcrumb fell back to the title-cased view name. It predates every merge this card was checked against:
none of #68, #69, #71 or #74 touches a view, a nav or either label. Reproduced under both direct-URL and
sidebar-click navigation, so it is not a navigation-path artifact. Nothing either caption asserts is affected.
Filed as #75 rather than ridden along here.

The committed 05, 06 and 07 also carry a Connected realtime pill that a fresh capture does not
reliably reproduce (it depends on whether the socket has connected when the shutter fires) — a reason to batch
any re-shoot of those, recorded in #75.

Gates

Run in the worktree, exit code captured before any pipe (cmd > file 2>&1; EXIT=$?):

VALIDATE_EXIT=0
LINT_EXIT=0
TYPECHECK_EXIT=0

pnpm lint includes pnpm check:i18n-source; its verdict line reads
✓ every bundle key restates the label its metadata declares at 512/512 keys. pnpm validate ends on the
same three pre-existing approver-staffing warnings as on main and exits 0.

All three were also run on the untouched base (e759ba5) before the first edit, and all three were
already green there. pnpm validate's output is byte-identical before and after — which is the expected
result for a docs-only change and the check the card asked for: had one moved, that would have been the
finding.


🤖 Generated with Claude Code

https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG


Generated by Claude Code

The Hiring Overview shot and its caption predated the fourth tile #55 adds
to `ats_employer_hiring`. Re-taken as `admin@quillstone.example` on a fresh
memory-driver boot at 818 rows, same recipe the set documents: headless
Chromium, 1440 x 900 viewport capture, cli 17.3.0.

The tile renders 30.0 (the dataset's `format: '0.0'`). Cross-checked against
the analytics query the widget compiles to, with the filter under
`selection.runtimeFilter` -- the only key the service reads; `body.filter`,
`selection.filter`, `filters` and `where` are each accepted with HTTP 200 and
silently dropped, and each answers 30.333... over every Quillstone
application instead of the nine hired ones. Compiled SQL for the correct
call: `SELECT AVG(days_to_offer) ... WHERE stage = $1 AND (employer_org IN
($2))`.

The other four caption numbers were re-measured the same way rather than
carried over, and none moved: 3 open jobs, 18 awaiting action, 6 interviews
this week, pipeline 10 / 8 / 5 / 2 / 1 (+1 rejected).

Two consequences recorded in the caption rather than hidden. The new tile
opens a second tile row, so Pipeline by Stage now starts below the 900 px
fold -- the same shape shot `01` has always had, so the pipeline values are
now cited as dataset-query measurements the way `02` cites the funnel's. And
on the demo seed the 30.0 is the hired applications' age, not elapsed
decision time (#65); the dashboard source says so and the caption now does
too.

Whole-set pass: all seven re-shot against this boot and compared to the
committed files. 01, 02, 04 and 07 are identical; 03 is this change. Neither
ripple reaches the set -- `last_activity_at` (#65) is rendered only by the
seeker `mine` timeline, which no shot frames, and Find Jobs is
`viewName: 'published'`, an authored ListView with an explicit column list,
so #33's `highlightFields` cannot move it (verified live: Job Title,
Employer, City, Work Mode, Salary min/max, Employment Type, 22 records,
unchanged). The only `filters` slices are the two platform review queues and
My Profile, none of them photographed.

Docs only: no `src/` file is touched, and validate / lint / typecheck are
byte-identical to their output on the untouched base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Review — I opened the image. The number in the caption is the number on screen.

For a screenshot card that is the whole acceptance, so I did not take it on report: I extracted both PNGs from git and looked at them.

The new shot (1440 × 900, same as the old one) renders four KPI tiles — Open Jobs 3, Applications Awaiting Action 18, Interviews This Week 6, and Average Days to Offer 30.0 in the second row — above a partially visible Pipeline by Stage chart showing its title, description, y-axis and the tops of two bars. The caption's five numbers are exactly those. The 30.0 rather than 30 is right too: the measure declares format: '0.0', so the tile renders one decimal and the caption matches the pixels rather than the query.

The crop, which is the judgement call in this PR

The fourth tile opens a second row and pushes the bars past the fold. You kept the 1440 × 900 viewport capture and moved the pipeline numbers into the caption as dataset-query measurements, citing 01 (whose weekly chart has always been cropped the same way) and 02 (whose funnel values have always been hover-only, hence query-measured).

I agree, and the caption is honest in the way that matters — it says the bars are cropped and says where the numbers come from, rather than quietly asserting values the reader cannot see. Becoming the set's only full-page capture to avoid a crop the set already contains twice would have been the worse trade.

You also carried the #65 caveat into the caption — that on the demo seed 30.0 is the hired applications' age, not elapsed decision time, because a seed cannot set created_at. I put that in the dashboard comment during the #68 review specifically so it would not be quoted as product history; finding it and propagating it to the place a reader actually meets the number is better than I asked for.

The trap, exercised rather than avoided

selection.runtimeFilter → WHERE stage = $1 AND (employer_org IN ($2))   → 30
body.filter             → WHERE (employer_org IN ($1))                  → 30.333333333333332
selection.filter        → same, no stage predicate                      → 30.333333333333332
selection.where         → same, no stage predicate                      → 30.333333333333332

30.3 is exactly the kind of plausible number that would have gone into a caption unchallenged. Printing the compiled SQL for all five measurements is what makes the table trustworthy rather than merely present.

The two ripples

All seven shots re-taken against a live boot and compared to the committed files, with only 03 differing. That is more than the card asked for.

One observation, filed not fixed

The dashboard now has an awkward 3 + 1 tile layout — one tile alone on the second row with three empty slots — and that is precisely what pushes the chart below the fold. Four w: 3 tiles on one row would fix both, but the tile widths are src/dashboards/, out of a docs-only card's scope, and "3-wide fits" is a guess until someone measures the title wrapping at ~280 px. Filing it rather than reaching for it here; if it lands, 03 gets re-taken once more and that is the correct order, not this PR's job.

#75 is a good catch and correctly not fixed — shots 05/06 carry a breadcrumb leaf one word shorter than a boot renders today, traced with git log -S to the same commit that added both the shots and the missing _views label keys. Churning two 100 KB binaries for a chrome word, and losing the Connected realtime pill in the process, is not worth it.

Merging.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 7, 2026 19:55
@os-sam
os-sam merged commit 85f1f02 into main Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release screenshot 03-hiring-home-overview.png predates the Average Days to Offer tile

1 participant