Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/734-unfiltered-first-run.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The first-run card now reads the account's unfiltered job count. A status, repository, kind or priority filter on the recent-jobs table no longer makes the first-run checklist reappear after the first job or changes the page's next action; the table still shows the filtered count.
5 changes: 4 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ defect.
the first job. Once the quota is exhausted or the platform model is not
configured, the same step becomes required again; the answer is derived from
the rows and config on every request, so no skip cookie or second store is
needed. Once a job exists the checklist is replaced by a first-run summary
needed. The checklist reads the account's unfiltered job count, so a
`status`/`repo`/`kind`/`priority` filter on the recent-jobs table cannot bring
the checklist back after the first job. Once a job exists the checklist is
replaced by a first-run summary
whose outcome is the first job's own status, read all-time (not a
window-scoped count), in four shapes: the
job's PR link when it opened one, "finished" with a link to Recent jobs when it
Expand Down
5 changes: 4 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,9 @@ export async function buildApp(options: BuildOptions): Promise<BuiltApp> {
const firstJob = store.firstJobForUser(viewer.user.id)
// #227: the checklist state is read from the database on this request, never
// from a cookie, so a second tab or a fresh browser sees the same step.
// #734: the checklist describes the account, not the job table. `jobsTotal`
// carries the current filter for the table; the checklist must not read it.
const setupJobsTotal = store.countJobsForUser(viewer.user.id)
const platformAvailable =
config.llm.baseUrl !== '' &&
config.llm.key !== '' &&
Expand All @@ -1241,7 +1244,7 @@ export async function buildApp(options: BuildOptions): Promise<BuiltApp> {
.some((provider) => store.listProviderModels(provider.id).length > 0),
platformAvailable,
instanceReady: allRepos.some((repo) => store.getRepoInstance(repo.id)?.status === 'ready'),
jobs: jobsTotal,
jobs: setupJobsTotal,
firstJobStatus: firstJob?.status ?? null,
firstPrUrl: firstJob?.pr_url ?? null,
repoHref: allRepos[0] === undefined ? null : `/repos/${allRepos[0].id}`,
Expand Down
28 changes: 28 additions & 0 deletions test/setupChecklist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,34 @@ test('a job that carries a PR replaces the checklist and the summary links it',
}
})

// #734: the table filter is table state. A filter that matches no job must not
// make an account with a finished first run look new again.
test('a job-table filter does not rewrite the first-run state', async () => {
const config = testConfig()
const store = new Store(':memory:')
const built = await buildApp({ config, store, mailer: new MemoryMailer(), runner: new MockRunner(), startScheduler: false })
try {
const user = makeUser(store, { login: 'checklist-filtered', status: 'approved' })
const session = sessionFor(built, user)
const cookie = `${session.cookie}; fledge_lang=en`
const repo = store.createRepo({ id: 'repo_filtered', userId: user.id, owner: 'o', name: 'filtered', defaultBranch: 'main', tokenEnc: 'x', permission: 'admin', repoRole: 'developer', goal: '', autoIterate: false, intervalMinutes: 1440 })
store.createJob({ id: 'job_filtered', repoId: repo.id, userId: user.id, trigger: 'manual', goal: 'g', callbackHash: 'cb', platformTokenHash: '' })
store.updateJob('job_filtered', { status: 'succeeded' })

const clean = await built.app.inject({ method: 'GET', url: '/dashboard', headers: { cookie } })
assert.match(clean.body, /data-first-pr/, 'the first job gives the account its first-run summary')

const filtered = await built.app.inject({ method: 'GET', url: '/dashboard?status=queued', headers: { cookie } })
assert.equal(filtered.statusCode, 200)
assert.match(filtered.body, /data-first-pr/, 'a filter that matches no job does not bring the checklist back')
assert.match(filtered.body, /Your first job finished/, 'the first-run outcome still reads the first job')
assert.doesNotMatch(filtered.body, /data-setup-next=/, 'the checklist is not rendered again')
} finally {
await built.app.close()
store.close()
}
})

// Finding 1 on #241: the first-run summary must read the *first job*, not a
// window-scoped success count. A success that fell out of the 7-day window used
// to render "Your first job did not succeed" and the retry path, while
Expand Down
Loading