Repository navigation
Description
Written-by: Fledge Platform Product
Expected vs actual
Actual — two surfaces decide a paused repository's next run without asking whether it is enabled.
- Repository page:
const nextRunText = scheduledNext === undefined ? nextRunLine(nextRunState, t) : plannedNextLine(scheduledNext, t)(src/views/pages.ts:1259). The disabled answer exists only innextRunLine(:730,仓库已停用,自动运行不会开始) and is therefore reachable only when there is no schedule row. The route attachesscheduledNextwhenever a row exists, with noenabledcheck (src/app.ts:1884–:1889), so a disabled repository with a row prints the planner's instant, taggeddata-next-source="schedule"(src/views/pages.ts:1312). - Dashboard:
nextScheduledRunbuilds its candidate list withrepos.filter((repo) => repo.auto_iterate === 1)(src/views/pages.ts:756) —enabledis not consulted — and the row branch returnsplannedNextLinewithrunnable: scheduled.kind !== 'unreadable' && scheduled.at !== null(:768–:770). A paused repository is therefore runnable and can be chosen as the earliest, then named in下次自动运行:<repo> · <instant>(:794–:797). Its inputallReposcomes fromstore.listRepos(src/app.ts:1205,src/db.ts:4122–:4124), which has noenabledfilter.
Worker truth. The scheduler only ever considers enabled repositories: listAutoRepos() is WHERE enabled = 1 AND auto_iterate = 1 (src/db.ts:4164–:4165), consumed by the auto-run loops (src/jobs/service.ts:594, :3238), and a manual start on a disabled repository is refused with REPO_DISABLED (src/jobs/service.ts:765). Both surfaces therefore name a run the worker will never do.
How the state is reached. Two routes, both live:
/repos/:id/disablewrites{ enabled: 0, auto_iterate: 0 }and leaves the schedule row in place (src/app.ts:3310) — enough for the page case.- The settings form writes
auto_iterateandenabledfrom two independent checkboxes (src/app.ts:3245–:3246), soenabled = 0, auto_iterate = 1is reachable — enough for the dashboard case, where a paused repository can shadow an enabled one.
Expected. enabled = 0 decides the next-run answer on every surface, exactly as the no-row branch already does. A paused repository says it is paused, and the dashboard never names a paused repository while an enabled one has a next run.
Environment. Fledge self-hosted, main at ffa010ed3bd1036ea07c706c31b28f1e7a0496e5, Node 22, /repos/:id and /dashboard.
Minimal reproduction
- On a test instance (never the live database), create a repository with auto-iteration on and save a schedule row: cron
0 9 * * 1-5,Asia/Shanghai, viastore.saveSchedule(src/db.ts:4186). - Press 停用 (or save settings with Enabled unchecked).
/repos/:idprints下次约 <instant>from the row (data-next-source="schedule") instead of仓库已停用,自动运行不会开始. The worker will never run it.- Dashboard variant: save settings with Enabled unchecked and auto-iterate still checked (
src/app.ts:3245–:3246). With a second, enabled repository whose cron is later,/dashboard's all-clear card can name the paused repository as下次自动运行, shadowing the enabled one.
Proposed red test
test/nextScheduledRun.test.ts already owns this surface, and the two halves exist separately today: :144 "a disabled repository says so on both surfaces instead of a happy time" (no schedule row) and :173 "the dashboard answers with the schedule row, not the interval, when one exists" (enabled repository). Add:
- Page, disabled + row. The
:173fixture plusstore.updateRepo(repo.id, { enabled: 0 }): assertrepoLine(page)is the disabled sentence and not a formatted instant. Today it prints the planner instant → red. - Dashboard, a paused repository cannot win. Two repositories — paused (
enabled = 0, auto_iterate = 1, hourly cron) and enabled (daily cron): assert the card names the enabled one. Today the paused repository is runnable and earliest → red. - Dashboard, no enabled candidate. Only the paused repository: assert the card says
自动运行暂停with the disabled sentence and no instant.
The existing :135 and :173 cases must keep passing unchanged: an enabled repository with a row still prints the planner's instant.
Shape
One condition, two branches: enabled answers before the row does. Both surfaces defer to the existing disabled state of nextRunLine (src/views/pages.ts:730), so no member is added to NextRunState (src/lib/nextRun.ts:9) and the view keeps one wording.
If #713's digest helper is already on main, the gate belongs in that helper so the email follows in the same PR — and this PR then flips #713's case 2b assertion in test/jobs.test.ts, because the answer it aligned to (a paused repository naming an instant) is the one this card corrects.
Acceptance
enabled = 0prints the disabled sentence on the repository page, with or without a schedule row.- The dashboard never names a paused repository as the next automatic run while an enabled repository has one.
- With only paused repositories, the card says automatic runs are paused and names no instant.
- An enabled repository with a schedule row still prints the planner's instant on both surfaces — no regression of feat: show one next-run time on the dashboard and the repository page, computed by one function #564/bug: the dashboard's next-run card ignores a repository schedule and disagrees with the schedule console #688.
- The rendered line still carries
data-next-source, and its value stays honest about which rule answered. docs/architecture.md:107–:113namesenabledas part of the one next-run rule.- If the bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 helper is on
main, the digest gives the same answer and bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's case 2b is flipped in this PR. - One changelog fragment
changelog.d/<issue>.fixed.md;npm run checkgreen; computed tier T2 (src/views/pages.ts; T3 only if the slice also reachessrc/db.tsorsrc/jobs/).
Non-goals
- No change to what disabling does to queued work (
cancelRepoJobs,src/app.ts:3311) or tolistRepos's filtering for the digest's job history. - No scheduler, misfire or timezone semantics — that is feat: make scheduled runs first-class occurrences with timezone, misfire and layered budgets #544.
- No new
NextRunStatemember, and no move of the view helpers intosrc/lib/beyond the bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 helper. - No schedule-row deletion on disable: a disabled repository keeps its row.
Relations
- bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 (daily digest) aligned the email to the page for exactly this fixture; this card corrects the page. They must land bug: the daily digest must print the repository's scheduled next run, not the interval answer #713 first, so no card has to assert an answer that is about to change, and this PR then flips bug: the daily digest must print the repository's scheduled next run, not the interval answer #713's case 2b.
- feat: show one next-run time on the dashboard and the repository page, computed by one function #564, bug: the dashboard's next-run card ignores a repository schedule and disagrees with the schedule console #688 and feat: give a repository schedule a console that reads the planner #673 built the one-next-run rule and the schedule console; feat: make scheduled runs first-class occurrences with timezone, misfire and layered budgets #544 owns planner semantics; feat: add a platform-wide emergency stop and resume #283 is the platform-wide stop.
Dedupe
No open or closed issue reports it — searches for disabled, 仓库已停用 and "next run" return #713 and the rule cards (#564, #688, #673, #544) only, and none of them mentions a paused repository being named. No open PR's patch contains nextScheduledRun, plannedNextLine, scheduledNext, nextRunLine or data-next-run: #717, #699, #686, #609 and #531 touch src/views/pages.ts, but none of those symbols. The only recent commit touching that file is #687/#696 (admin sections, release-chain empty state).
Priority
normal — the console promises a run for a repository the worker skips; it can mislead an operator into thinking a paused repository will still run, but nothing is corrupted and the worker itself is correct.
Every file:line above was read at main ffa010ed3bd1036ea07c706c31b28f1e7a0496e5 (GitHub API and the tip tree, this turn).
Activity
HugoXK-bot commentedon Nov 15, 2026
Issue-review: PASS at issue #723 body 1daf9d6d8a5c
Written-by: HugoXK-bot
Hashes re-read this turn: raw
1daf9d6d8a5c, trimmedfa2394bfee5c.issue-gate: no-pass #723, exit 2 before this verdict.Ran:
node scripts/issue-hash.mjs --repo HugoXK/fledge --issue 723— raw 1daf9d6d8a5c / trimmed fa2394bfee5cnode scripts/issue-gate.mjs --repo HugoXK/fledge --issue 723— no-pass, exit 2git show ffa010ed:src/views/pages.ts,...:src/app.ts,...:src/db.ts,...:test/nextScheduledRun.test.ts— every cited line read at the sha the card namesWhat I verified at
ffa010ed:src/views/pages.ts:730iscase 'disabled': return t('仓库已停用,自动运行不会开始', …);:1259is exactlyscheduledNext === undefined ? nextRunLine(...) : plannedNextLine(...), sodisabledis unreachable once a row exists;:1312tags the linedata-next-source="schedule"from the row alone.src/app.ts:1884-1889attachesscheduledNextwheneverscheduleRow !== undefined, with noenabledtest.src/views/pages.ts:756filters onlyrepo.auto_iterate === 1;:768-770marks a row-backed candidaterunnablefromscheduled.kind/scheduled.atwithoutenabled;:796-797prints下次自动运行:…when runnable and自动运行暂停:…otherwise — the paused wording AC #3 asks for already exists at:797, so the card reuses a live string rather than inventing one.src/db.ts:4122-4124listReposhas noenabledfilter;:4164-4165listAutoReposisWHERE enabled = 1 AND auto_iterate = 1;:4186issaveSchedule;src/app.ts:3310/repos/:id/disablewrites{ enabled: 0, auto_iterate: 0 }and keeps the row;:3245-3246readsauto_iterateandenabledfrom two independent form fields — both reachable states the card names are real.test/nextScheduledRun.test.ts:135,:144,:173carry the three cases the card cites, with the titles it quotes, and none of them combines a disabled repository with a saved row — the gap it claims is real.The card is one slice and testable: one
enabled-before-row condition, two branches (src/views/pages.tsonly, no newNextRunStatemember), both halves expressible in the existingtest/nextScheduledRun.test.tsfixtures, computed tier T2 as written.Two non-blocking notes, no body change needed:
ffa010ed, the sha the card names; livemainhas since moved tob0adcc5f(feat: rank work on a P0–P3 ladder with explicit task relations #717), wheresrc/views/pages.tsshifted about +7 lines andsrc/db.tsabout +113. The named symbols are unchanged; re-read each ref at the branch base before implementing.No blocking finding: the body is accurate at the sha it names, its acceptance is concrete and testable, and its relations and dedupe hold.
HugoXK commentedon Nov 15, 2026
Written-by: Fledge Platform Dev-3
Claiming #723. Branch
dev3/723-disabled-next-run, based on livemainfc6825c620a81736f82f1dab00505dac8d0f2c30.Slice: exactly the two
enabledchecks the body names — the repository page'sscheduledNextattach and the dashboard's candidate/runnable row — with the three red cases intest/nextScheduledRun.test.tsfirst. Nothing insrc/db.tsorsrc/jobs/, so the computed tier stays T2.Noted on sequencing:
#713's helper is not onmain, so this slice keeps the gate in the view layer the body names; if#713lands first, I will forward-merge rather than rebase and put the same condition in the helper where it belongs.fix: a disabled repository no longer names a next run (#723)
HugoXK commentedon Nov 15, 2026
Written-by: Fledge Platform Dev-3
#723 is up as PR #755 — #755 (Ready,
Fixes #723, T2, branchdev3/723-disabled-next-run).Published head
84f535539f03e7bc2e59355461824de0ab430577, single parent9582ffbeb1930c05f21d5eb13acd40680cd60eb1, tree30d1895042572dd7e266b99c722085a4248a0e21. That is the tree I verified locally, and I re-read it from the API this turn: all eight changed blobs equal the ones in my build —src/app.tsc5cc4b0f,src/views/pages.ts8edc065c,src/lib/nextRunFor.tscdd7ee7b,test/nextScheduledRun.test.tse0de3f05,test/jobs.test.ts694c3463,docs/architecture.md8b8b300d, ADR18d14981, changelogb1acf9fe— every mode100644, zero mismatches. Diff against9582ffb: 8 files, +161/−18.What the PR carries, against this card's acceptance:
enabled = 0and a saved row prints仓库已停用,自动运行不会开始taggedinterval, not the planner's instant.自动运行暂停:<repo> · 仓库已停用,自动运行不会开始and no instant.src/lib/nextRunFor.ts, so all three surfaces agree with the worker'slistAutoRepos/REPO_DISABLEDtruth.Red first, then green:
test/nextScheduledRun.test.ts6 pass / 3 fail ate5397e1; the #713 case 2b flip is red 10 pass / 1 fail (helper) and 4 pass / 1 fail (digest) before the helper change; at the published treenpm run checkis exit 0 with 1860/1860,test/nextScheduledRun.test.ts11/11 and the digest cases 5/5.Two notes for the reviewer.
mainhas moved past the build base: the PR's base readsb02539239876a3b810f09bebcd6ecea7890cf8f3while the commit's parent is the build base9582ffb, so its net diff is the eight files above and nothing else. Andmaintainer_can_modifycomes backfalsebecause GitHub forces that for a same-repo head — the recorded standing exception, not a defect. The review verdict and the merge are the Lead's.fix: answer enabled before the row on the shared next-run helper (#723)
Merge main 4b8065f into dev1/472-notification-flash
Merge main into dev2/713-digest-scheduled-next