Repository navigation
Initiatives, hidden drafts and a Roadmap range picker - #532
Merged
Merged
Conversation
Initiatives are one layer above projects: a name, a goal, an owner and a rank of their own, and one home per project (an initiative, Keep the lights on, Not tracked, or no row for Unsorted). The project slug is the primary key of initiative_projects, so a project can't sit in two. set_at and baseline_prs are where the not-tracked growth check starts counting from. 0039 makes both tables, so each has its own MIGRATIONS entry, as 0028's do; schema.sql and bin/dump-schema list them too. Nothing reads them yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server and the board both need the same rules for the new layer, so they live in shared/: an initiative's shape, the checks on what a person sends, its span read off its projects' plans (parked and dropped plans don't count; done ones count toward the start), the growth check (30 PRs and at least as many as it had), and each project's home. decideQueue takes the initiatives and the grown not-tracked projects and asks four new things: unfiled, untracked_grew, goal_reached and reopened_in_closed. Nothing asks where a project belongs while no initiative exists, so the empty tables change nothing on day one. A not-tracked plan owes no update: healthStanding and planWarnings stay quiet for it. The board's Decide gets plain placeholder words for the new reasons so its switches stay whole; no board caller passes initiatives yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A renamed P- label moves what Pulldasher keeps by slug, and an initiative filing is now one of those things. It moves exactly when the roadmap item does, so a plan left on the old slug for a person to merge keeps its home. When both slugs are filed, the new one's home wins and the old row goes, the merge rule everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/initiatives.js reads and writes the two tables: list in rank order with each initiative's projects, create (ranked last), edit, delete when empty (the undo of a create, freeing the slug), move one above another, and file a project in one home or none. Filing takes the home the person saw (`expect`) and answers 409 with the current one when it changed, and it hands back the home it replaced so an undo can put it back with its set_at and baseline_prs. Filing into Not tracked starts the growth check: the PR count sent, else the old baseline plus the PRs since (Keep), else the count now, counting only labels that sit on a pull, since pull_labels holds issues' labels too. The same handlers serve the board under /initiatives and the API under /api/v1/initiatives, each write behind canWrite. Closing an initiative is refused while a project in it has a plan under way or open PRs. PROJECT_WRITES moves into api-routes.js beside the table it guards, so a test can check every API write is one open boards hear about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project filed in Not tracked, or whose every labeled PR sits in a repo listed in the new projects.untrackedRepos (left unset), stops counting. The three endpoints that count run one filter, trackedOnly: Decide drops those projects, the load drops them past and ahead, and updates-owed drops their plans. Each roadmap item now carries `untracked`, set in `sent` before its standing is read, so a not-tracked plan's standing is quiet everywhere the item goes. A database without the new tables still reads its roadmap. The growth check counts PRs opened since a project was set not tracked against its baseline. Decide asks about the ones that grew, and asks where a tracked project with no home goes once any initiative exists, so the empty tables change nothing on day one. Each decision names its initiative. /projects-data gains the untracked and grew slug lists (with each grown project's counts), and /api/v1/projects gains initiative, tracked and grew per project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The board loads the initiatives from /initiatives on first use, like the roadmap, and changes them in place with the server's answer to follow: filing a project, moving an initiative, closing or keeping one open. Not tracked projects leave the board's data where it comes in, so every list, count and question reads tracked data unless it asks for the full set. useRoadmap() and readRoadmap() drop a plan the server flagged untracked, or one a filing made here since puts in Not tracked; useProjectsData() drops their issues and numbers unless it's asked for all of them or one project. A reorder of the tracked plans is merged into the whole order PUT /roadmap/order needs, each Not tracked plan keeping its place. The one-slug readers (a plan's facts, a row's details, the backlog chart, People's charts, the review board's project standing) read the full set. The dummy board gets initiatives for every standing a lane can take, a Not tracked project with a plan that grew, and one with none, so frontend:dummy shows them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decide answers the four initiative questions in its own shape: the suggested answer filled, the rest a click away. In no initiative: File in the chooser's pick. Still not tracked: Keep not tracked, which starts its count again, or Track it. Did it reach its goal: Close as done, Close as dropped, or Keep open. Its initiative closed: Reopen it, or move the project. Each leaves a receipt with Undo, which puts back the home with its time and count, or the initiative's status with its times. A new or unfiled row carries an Initiative chooser, on the project's home so taking an answer blind moves nothing, or on a guess (where its people work most) only in Unsorted. A plan call with a new pick files first and plans second, one receipt for both; Undo removes the plan, then the home. Rows about an initiative name its owner and open its page, and an Initiative switch beside the team switch gives an owner their slice. The initiative page shows its projects, span, owner, goal, what its plans owe and a Done or dropped fold, with the question Decide asks of it. A project's page says its home with Move. Look back splits days by initiative. Group by Initiative is the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The project list groups by initiative, in their rank, each band saying its owner and how many of its plans have an end or run on; Unsorted comes last only when it holds a project, and the find box takes "initiative:". Not tracked projects close the list in a ledger that rests closed and counts in no tab, while the tiles, charts and tab counts read only tracked projects. Yours and Who is on what read every project, so your Not tracked work stays yours and per-person counts agree with People. My work asks Decide's questions of tracked work only, and keeps every project of yours on its list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grouped by initiative, the Roadmap opens on one band per initiative in its rank: the rank, the name, the rows it holds and one ink phrase of its dates and counts, read off its projects' plans. A lane holds its plans under way, its live projects with no plan, and any plan Decide asks about, and rests closed unless it holds a call, so the bands read as a table of contents. Its grip sits in the gutter, outside the band's click target: drag it onto another band, or use the arrow keys, to rank it. Dropping a plan on another initiative files its project there and leaves its priority alone, with Undo; Move in the plan's details is the keyboard's way. A link that opens a plan opens its lane first. Not tracked plans and projects close every grouping in their own lane, with no rank and no drag; the load chart, the capacity line and Now, next, later count tracked plans only. Now, next, later splits by initiative too. The Projects tab now builds the tracked and the full sources once and hands each view the set it reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DESIGN.md says why the Roadmap and the Overview open by initiative, and the three exceptions initiative lanes make to the Fold and lane rules: a lane holds only what's under way, its grip sits in the gutter left of the band, and it rests closed unless it holds a Decide call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The close guard counted a project with PRs open as still going, but Decide's goal_reached didn't, so Decide could suggest a close the server refused with a 409. goingProjects (a plan under way, or PRs open) is now the one rule; the guard calls it, and Decide does in the next commit. Which projects are Not tracked was worked out twice on the server, once cached and once on every /projects-data, /api/v1/projects, /decide and /load request. trackedNow keeps one tracking() computation for every controller, forgotten with the other project caches on each write. The roadmap's fallback for missing tables goes with it: schema-check already stops a server without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review fixes on Decide and the initiative page: - goal_reached and reopened_in_closed use goingProjects, so Decide never suggests a close the server refuses; Keep open holds through PR activity on finished projects, waiting for a plan or filing change. - reopened_in_closed joins the project's own row, ahead of reopened, so one project no longer gets two questions with clashing suggestions. The reopen's receipt stays when the row comes back for its plan, and that plan's answer then restarts it rather than keeping it done. - A new no_project reason asks an active plan with no project which project it is; Link to <project> answers it, with Undo. - Not tracked is found by its kind, never the 'not-tracked' slug. - The home UI moves to Initiatives.tsx. Choosers sit above the buttons they feed, all say "File it in"; an answer waiting on an empty chooser is off; Suggested marks only the board's guess. - "Unsorted" is the one word for a project filed nowhere. - A goal question shows the goal. A filing 409 names where the project went. A failed edit Undo says why. - The initiative page asks its projects' questions on their rows and its own at its head, counts them in Health, folds only finished work with nothing asked, and treats Not tracked as a ledger. - primaryOf and bandGloss each exist once; the board's untracked helpers are renamed so the server's names mean one thing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Roadmap review fixes under Group by Initiative: - Rows are numbered by their place in the lane, under the band's rank; the whole-roadmap priority moves to the grip's label and a hover, so two unrelated numbers no longer sit side by side. - A cold link or a reload to a plan opens its lane: the link effect runs again once the initiatives arrive and the rows sit in lanes. - Plans with no project get a "No project yet" lane, so Unsorted holds projects only and agrees with the Overview, and its hover no longer promises a drag that fails. The plan's details say why it has no home. - While the find narrows, an initiative's own question shows only when the find matches it. - The lane line names the owner as such, a past latest end says so, the Not tracked hover is the shared sentence, and Now/next/later carries the lanes' ranks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Onboarding's "Did it reach its goal?" counted in the To decide tile but had no band on the Overview, since none of its projects is being worked on. An initiative Decide asks about now keeps its band in the tab, with the goal, the question and its answers inside. The band's hover is the Roadmap's (goal, total, working, owner), and the Not tracked group uses the shared sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Look back's split by initiative lumped Unsorted projects, small work and PRs in no project into one "No initiative" row, which read as nearly half the team working outside any initiative. They are now Unsorted, Not tracked, "Small work, no project" and "Not in a project", the Projects table's words, after the initiatives in rank. Not tracked is one group by kind, whatever its initiative's slug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DESIGN.md's initiative section now says how lane rows are numbered, that "Unsorted" is the one word for a project filed nowhere, and where a plan with no project goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Did it reach its goal?" waited two weeks after the last plan stopped, but a filing's set_at only counted toward Keep open. Filing a project whose plan finished long ago asked at once, so the load's 242 filings would have asked on day one, against "nobody owes anything new". A project now settles in an initiative two weeks after it is filed there too. Keep open still comes back on a later filing, two weeks on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /roadmap and every roadmap write's reply awaited the tracking read beside lately(). A failed read of the initiatives tables 500ed the roadmap, which never depended on them before. The roadmap now reads every plan as tracked and logs it, as lately() does; /decide and /load still fail loud, since their counts would be wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The close guard was tested at the endpoint with plans only. Its open-PR branch, the round-1 bug, was covered only through decideQueue. A done plan with a labeled PR still open on the board now 409s at PATCH, and closes once the PR does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
listInitiatives, grownSlugs and the Overview's initiative groups built each list by spreading it again per row, quadratic in a project's PRs. grownSlugs now counts the PRs after the filing directly, since only the count is used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decide.tsx and Initiatives.tsx imported each other, which works only while neither reads the other's bindings at load; and Decide.tsx and Roadmap.tsx had grown by about 450 lines each. This commit moves code and changes nothing it does: - model/decideCalls.ts: Decide's question model with no components (sections, what each reason asks, answer and call words, the Call and Filed types, AllItemsContext, arrowsAmong). - HomeCalls.tsx: the four home parts Decide draws (useHomeChooser, HomeChooser, HomeStrip, InitiativeSwitch), reading only the model. - RoadmapLanes.tsx: the initiative lanes (fileByDrag, rankMove, the lane drop and grip, the lanes themselves) as initiativeLanes(), with TimelineLane and ReceiptLine, which they draw. Importers now read the model directly. madge finds master's two cycles and no new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three words named one order (rank on lanes, priority on rows, place in the hints), and two labels pointed at numbers the row doesn't show: "Starts before #2 is done" printed the whole roadmap's index while lane rows count 1, 2, 3, and a row moved in a lane said "priority 37". - A clash names the plan it waits on: "Starts before Shopify product and order sync is done" reads the same in every grouping. - Lanes and rows both say priority: grips, hovers and receipts. - Under Group by Initiative a row's receipt and its Undo say the number the row shows, with its lane: "to priority 1 in Site speed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receipt for an initiative's move was drawn inside its lane and again below it whenever the lane rested closed by default. A lane that rests closed but was opened showed it twice; one that holds a call but was closed showed it nowhere. It is now drawn once, under the lane, whatever the fold's state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plan with no project lit up any initiative's lane as a drop target, and the drop did nothing anyone could see: the reason was only in a screen-reader region, and it pointed at Edit details. The lanes and rows of other lanes now refuse that drag, so nothing lights and the cursor says no. If a drop still lands, the row says why with "Open its plan", where its details ask which project it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One end date gave two counts in one lane: the band floored the weeks
("2 weeks past") while the plan's row and bar rounded up ("3 wk
past"). weeksPast in shared/model/roadmap.ts now rounds up for every
view: Decide's reasons, the Overview's cells, the rows and the band.
A closed initiative read "Ended Sep 20" on the Roadmap and "closed as
done" on the Overview; both and its page now say "Closed as done
Sep 20" (closedWords). An open one whose plans have all stopped says
"Nothing under way since Sep 20" instead of "Ended", which read as
closed. The Overview band names the owner ("owned by X").
Bands with no grip keep the grip's column, so every lane's label
starts at one x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was a button styled as a link, so it couldn't be opened in a new tab, cmd-clicked or copied, though the page has an address. It is an <a> to the board's hash with the initiative set; a plain click still navigates in place, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R5 says a filing that rides with a plan call stays when the plan fails, and the row says "Filed in X; the plan didn't save" with Try again. That path had no test. Now one plan's POST fails once: the row says so, and Try again sends only the plan, since the filing's expect is stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UNSORTED was 'unsorted', a valid initiative slug: an initiative made with it would share a Roadmap lane key (duplicate React keys, one merged lane) and Decide's within=unsorted filter. Unsorted is now '(unsorted)' and plans with no project '(no-project)', which PROJECT_SLUG refuses; Look back's split reuses the first. Decide's Initiative switch counted a plan with no project under Unsorted (4 where the lane said 3). It now has its own entry, No project yet, as on the Roadmap and in Decide's sections. With a team and an initiative both picked, the heading names both: "for Store in Site speed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Undo receipts read three ways: "Put Newsletter promo page back", "Put
it back in Store sync and checkout" and "Undid the decision on Visual
regression checks". Each now names what went back and where: a drag's
Undo "Put Image CDN back in Site speed", a Move's "Put cache back in
Site speed", a priority "back at priority 2", and a Decide call "Put X
back as it was", the bulk and park ones too.
A Move's receipt names the new home, so the home word beside it is
hidden while the receipt shows ("Not tracked ✓ Moved to Not tracked"
said it twice).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No test rendered the Roadmap with initiatives; the lanes were checked only in a browser, which is where round 2 found the doubled receipt. RoadmapLanes.test.tsx renders it against a fake server: - the lanes in priority order, then No project yet and Not tracked, the closed one hidden while it holds nothing - rows numbered by their place in a lane, the roadmap's on the grip - ranking a lane with the arrow keys, one receipt whether the lane is open or closed, and Undo back above the one it sat above - a plan dropped on another lane files its project, leaves the order, and Undo files it back - a plan with no project refused by lanes and other lanes' rows - a row moved in its lane says the number it shows - a link to a plan opens its lane; "Initiative page" has an address Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Roadmap's goal row outlines its answers to sit quietly beside rows whose questions are words, and in doing so it dropped the Suggested mark that Overview and Decide show on "Close as done", so the board's own guess vanished in one view (UX review 7, L5). The outlined button keeps the mark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the Overview's "Unsorted · 3" band, a row's Plan cell said "Unsorted" in amber, the band's own name, where its siblings named a call (UX review 7, L6). It asks Decide's question instead: "Which initiative is it in?". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An eaten press outlined its control with the 2 px focus ring for 0.7 s. Under a still pointer, with its hover border too, the control looked picked or focused while the focus was elsewhere, and in dark mode the grey ring barely showed (UX review 7, L4). It now dims to 45% with a 3 px nudge either way for 360 ms, which reads as "not yet" in both themes. Under reduced motion it dims without moving and takes a dashed outline in its own colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard's rule as it now runs: the control just pressed is judged first, boxes are centre and layout size, a browser double-click on it is one click (an opener's too), aiming needs a rest or a double-click's span, any key but a modifier ends a hold, and the kept sign is a dim. It also says the touch rule out loud: a tap on what came under a still finger waits out the hold, since a finger has no move between taps. Also the wording: one receipt for filing into Not tracked, Not tracked capitalised as the home's name, an Undo's name without the full stop and a failed one naming its receipt, Suggested on the Roadmap's goal row, and Unsorted's Plan cell asking its question. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kyle: draft PRs are common and mean essentially nothing, they're just a prompt. A draft isn't work yet, so the one rule now lives in buildToday, which the board and the server both run: a draft goes to its project's `drafts` (or Today's `drafts` when no listed project holds it), never to `open`, misc, unsorted or two projects, and a draft closed as one never counts as leaving. Idle days, people, flags and live vs quiet read the real PRs, so a project with only drafts is quiet when its issue is open. The server half follows: planWork skips drafts (Decide's open, after-end and after-done counts), the window's spans and the days spent leave drafts out (throughput, backlog, who is on what), and /api/v1/projects lists each project's drafts and the rest beside the counts. The project page's lookup of live rows takes drafts too, so a draft there still draws as its row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draft isn't work yet, so on a project's page it leaves every stage
and count: its PRs not tied to an issue end in one fold, "Drafts, not
counted", closed by default. A draft under an issue still shows there,
says "a draft, not counted", and its issue's stage and "N PRs" door
leave it out ("1 PR, 1 draft"); an issue whose only PR is a draft has
no PR yet. The summary adds "2 drafts, not counted" under the open
counts, which brings them into view like every other count there, so
"None open" never reads as nothing at all.
The stage glosses stop saying "a draft" is in development, and the
board's live row says whether a PR is still a draft, newer than what
the page was sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With drafts out of every count, each place a lead would look for them now has them in a closed "Drafts, not counted" fold: the Overview's PRs outside projects (drafts no listed project holds), a project's row opened in the list, and a person's row on People. The drafts fold on a project's page drops its rows' own "a draft" note, since the fold's name says it. Rows stay as calm as they were. The Open column's hover says "and 1 draft, not counted" when a project has some, and so do the roadmap's open count and Decide's facts. "Drafts included" on the Open column and on who is on what said the old rule; both now say drafts are left out. With only drafts outside projects, that group says "None open, 1 draft, not counted" rather than "0 open PRs". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake board had one draft and no project to show the rule on. Newsletter promo's two PRs are drafts now, a project with a plan and only drafts; one of MySQL 8's is too, a mix, and it's the viewer's, so People shows a person's drafts; and a one-off's lists under PRs outside projects. The fake board's window and days leave drafts out the way the server's do, so its backlog chart agrees with the counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Next to "a PR counts where it links": a draft isn't work yet, so no count takes one, and each waits in a closed "Drafts, not counted" fold where someone would look. Says where those folds are and how a line that says nothing is open names the drafts, so the next view follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The timeline's window was always built from today, so after a year of plans was backfilled nobody could move it back to see them. A date range picker now sets the window, kept in the URL as `window` (a preset or "2025-10..2026-09"); with no key the roadmap opens and links as before. Its columns follow its length, the arrows step by the window's own length on every view, Today puts back the default, the edge chip moves the window to its plan, zooming out keeps your place, and done plans that ended before the window rest in one closed fold. Also: a done or dropped plan's details no longer offer Decide's calls, and "All quarters" no longer wraps on a phone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window picker, the steps back and the "Done before" fold only show
anything with plans older than the default window, and the dummy board's
oldest plan started 11 weeks ago. Fourteen plans from 18 to 6 months
back, done and some dropped, make every standing visible on the bench.
Also from the bench: the switch shows the columns a window is drawn in,
the header drops the Mondays' dates past seven month columns, a picked
preset's months show only on the widest screens so the toolbar keeps to
one line at 1440, the initiative lanes (which hold only work under way)
have no done fold, the load chart's "Next 4 weeks" words step aside when
today sits at the window's right edge, and DESIGN.md and the timeline's
help say how the window works ("one range per view").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A blind review found the "Done before" fold hid a done plan Decide asks
to reopen (and the initiative lanes lost it), a picked week left the
fold full of plans the chart didn't count, the 8% sliver rule ate
February and every name on long windows, the Months and Quarters switch
threw a picked window away, and a window wholly ahead asked the server
for an inverted range.
Now a plan Decide asks about is never folded and the fold follows the
picked week. Month and quarter names shorten, turn to letters or thin
out by measured width, every column keeping its zoom button. The switch
redraws the same window in the other unit, does nothing on the lit
option, and Now, next, later keeps the window. A window ahead asks for
no history. Windows stop at five years, a longer link is cut and the
URL rewritten. Presets keep their own unit whatever the month, the URL
says a custom span's unit ("2025-Q4..2026-Q3"), Today hides on a preset
that holds today, the picker is named "Roadmap window: ...", and the
developer line's name moves to the end with lower bars.
Render tests now cover the toolbar, the edge chip and the fold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /initiatives was added in app.js but never registered behind the sign-in gate in lib/authentication.js, so anyone could read every initiative's name, goal, owner and filed projects. Writes were already behind canWrite. The test reads app.js's GET routes and fails if any isn't gated, so the next new read can't ship open the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
initiative_projects.project compares without case, so after the UPDATE renamed 'Docs' to 'docs' the DELETE of 'Docs' matched the renamed row and dropped the project's filing. The delete now compares BINARY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slug sent in another case is now stored and answered as its label spells it, and expect compares without case, so the board finds its home. Unsorted is refused for a project Not tracked by its repos, since deleting the row left it Not tracked while answering home: null; an undo (undo: true, now sent by the board's three Undos) may still put a repo default back. Undo also sends expect, so it can't overwrite a later filing. The growth check counts no drafts or bots on either side, baseline_prs is capped, and filing, closing and deleting run one at a time so a close or delete can't pass its check while a filing lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pulldasher kept only the current draft flag, so a draft marked ready counted from the day it opened: past windows' throughput and backlog moved afterwards, time spent took in its draft weeks, and Decide placed it by its open day. Migration 0040 adds pulls.ready_at, which the upsert stamps when a stored draft is saved as ready and clears when it goes back to draft. Spans, time spent and the work model read it. Rows stored before it stay NULL, so a PR readied before the migration still counts from its open day: there's no history to backfill from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit compared the form with the initiative as it is now, so a field someone else saved since counted as changed and was put back; it now compares with the initiative as the form opened. The page counted questions answered this visit as still owed, and waited forever on a failed load instead of offering Try again. Group by Initiative keyed its bands by name, which two initiatives can share. And a Not tracked project's drafts no group lists showed in the Overview's drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Undo of Reopen sent the old closed status, which the close check refused because the project that made Decide ask is still going in it; an undo now skips that check, on the server and the fake board alike. A failed write restored the whole list from before it, undoing other writes still in flight; now it takes back only its own change and reads the list again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening an initiative's page changed no key viewChanged compared, so it replaced the history entry and Back left the board; every key of the view is compared now, and the page opens at its top. A parent link set group=parent, which the switch no longer offers; unknown groups read as the default. week= took impossible and non-Monday days, and window= and zoom= took years 0000-0099 and 9999. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plan saved Done, Parked or Dropped from its details on the initiative lanes, or moved so it ends before the window, vanished or folded away with its Undo; the open plan and the one whose receipt shows now stay. A week picked on the load chart counts only while the chart draws it, so the headline, chip and rows agree after a step. A window past the server's 400 days of PR history hatches the weeks before it and says where history starts. Lane glosses count the rows they head, a past window's history stops on its last day, a drag to the track's edge lands in the window, and a filing reads the roadmap again so a repo default's Not tracked flag is fresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With about 290 plans every change re-rendered for 1.3 to 2.8 s of long tasks on a production build. dayWords built a date formatter per call, every row drew every week line past the seven months whose week dates are already dropped, and loadByWeek worked out each plan's end once per week. Measured on one press on a bar: 1251 to 149 ms on the default window, 1382 to 146 ms on 12 months, 1757 to 209 ms on 5 years in quarters, and 2803 to 317 ms on 5 years in months. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A truthy but empty undo skipped the close check; only one with times to
put back does now. Look back credits a former draft's ready day as the
day it opened (0041 indexes ready_at for that read). Roadmap status
writes, restores and creates, and initiative creates, run through the
same one-at-a-time chain as initiative writes, and every query has a
60 s timeout so a hung one can't stall it. The work model keeps
createdAt as the open day and carries readyAt beside it, so "opened"
stays true on screen while planWork and the late words go by the ready
day ("marked ready after the end date").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a home answer on a row that also asks about its plan, the row stays owed for the plan, so the tab badge, the Initiative and team switches and the initiative page said 1 while Decide's heading said nothing was left. keepCalls now returns `open`, the owed rows not answered this visit, and every count reads it. The plan question still comes on the next visit, as Decide already said; asking it at once would need a second state on the row beside its receipt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Not tracked plan saved Done from its details vanished with its Undo;
that lane now holds the open plan and the one with a receipt, as the
initiative lanes do. Opening a plan inside the Done before fold pulled
its row out of the fold, far up the page; a held plan now stays in the
list the last render drew it in (the plan being moved counts as held,
so a move out of the window still keeps its Undo in view). Month names
that shorten keep the year on the first column and each January
("Jan ’23", then "2023"). A step past 1970 or 2999 stays put instead of
jumping to the default window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit gave every query on the shared pool a 60 s timeout so one hung query couldn't stall the initiative writes that run one at a time. But bin/migrate-missing and the backfill bins share that pool: a migration altering a big table past 60 s would have its connection dropped while MySQL kept running the ALTER. serially now gives each step 60 s instead: a step that hasn't finished fails its own request and lets the chain move on. No query is cut off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Final verify: a roadmap PATCH that only moved a plan to another project skipped the one-at-a-time chain, so an initiative close could pass its check while a plan under way moved into it. A project change decides whether a plan is going in an initiative as much as a status does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
🤖
Managers can't organize 192 projects one by one, and Kyle asked for a layer above them. This adds initiatives to the Projects tab. It also hides draft PRs from every count and lets the Roadmap show any range of months. It has been live on prod and dev since Oct 12, and the year of project data seeded on Oct 13 already uses it. Merging it means a rebuilt cominor deploys this code instead of master.
Important
Three migrations, run with
bin/migrate-missingbefore the new code starts:0039adds theinitiativesandinitiative_projectstables. Already applied on prod and dev.0040addspulls.ready_at.0041adds the index onpulls.ready_at.The code reads
pulls.ready_at, so 0040 has to run first.What changes
Initiatives (
lib/initiatives.js,controllers/initiatives.js,views/projects/Initiatives.tsx,HomeCalls.tsx)GET/POST/PATCH/DELETE /initiatives,PUT /initiatives/fileandPOST /initiatives/:id/move. Writes that check and then change an initiative run one at a time, so a close can't race a filing.Drafts hidden by default (
shared/model/projects.tsbuildToday)pulls.ready_at, migration 0040). Rows from before this migration count from the day they were opened, as they did before; there's no backfill.Roadmap window (
model/roadmapTime.ts,Roadmap.tsx)window=key is left out at the default, so old links still open as before. the arrows step by the window's length, and Today resets.Also
components/settle.ts) swallows a press only on a control that moved under the pointer.DESIGN.mdgains the rules these features set: one range per view, drafts never counted, Undo names, and others.How it was checked
GET /initiativesanswered without sign-in.test/auth-routes.test.jsfails if any GET route inapp.jsisn't behind sign-in.tscclean, 0 lint errors.Bug-hunt findings and their fixes
The full reports, with the scenario, evidence and fix for each finding, live outside the repo (
pulldasher-initiatives/build-reviews/bughunt-{server,views,roadmap}.md). The worst:GET /initiativeswas readable without sign-in (fixed in30b9afd5).Docs->docs) deleted the project's filing, because the collation ignores case.Not in this PR
undo: true, or undoing an ops project's move back to Unsorted gets a 409. That's committed on the skill's branch and goes out with that branch's PR.🤖 Generated with Claude Code