Skip to content

Treemap, bare-command pickers, jobs menu and schedules, background chart priming, 6 h cache, CI on Node 24 - #41

Merged
robdevops merged 42 commits into
mainfrom
claude/kind-newton-ad0z2q
Oct 16, 2026
Merged

robdevops merged 42 commits into
mainfrom
claude/kind-newton-ad0z2q

Conversation

@robdevops

@robdevops robdevops commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

What this brings in

40 commits since the last merge (#40), grouped by what they change.

.treemap and the pie

  • New /treemap PORTFOLIO [sector]: the holdings as tiles sized by value, grouped and coloured by industry (default) or sector, with group headings that fit their block, 2px edges and the subtitle beside the title.
  • /pie legend text is bigger (13.5pt), shrunk only where a name would run into its share; the pie is a little smaller to make room.

Pickers for a bare command

  • /pie, /treemap, /history and /compare typed with nothing answer at once with the first portfolio / first ticker / first two tickers (alphabetical; /compare starts on 7D), with the picker's buttons kept under the chart. /holdings shows its portfolio buttons.
  • Portfolios four to a row, /treemap's Sector/Industry toggle after them; tickers behind a menu (Tickers ▶, Select... (n) for compare) with 32 items a page and < > (fewer on Discord's five rows of five). Compare ticks up to six, refuses a seventh with an alert toast; Compare and picking a history ticker go back to the top level, which has the period row.
  • /compare T1 T2 [period] typed with tickers gets the same buttons when the tickers are among those held or watched.
  • Telegram keyboard: .compare, .pie, .treemap in place of .dividend, .marketcap, .pe.
  • A Slack chart message keeps its button rows; Discord keeps rows too.

Background priming

  • After a pie/treemap goes out, its other grouping and every other portfolio are drawn in a thread; every chart with a period row (compare, history, performance, price list, the pickers) draws its other periods. Never for a scheduled run.

.jobs and schedules

  • Jobs without cron (lib/when.py schedules, default jobs in every chat, message jobs, the .jobs menu with Add down to the minute, Upcoming..., jobs that run .help commands, finbot without Sharesight).
  • Menu: Remove is now Delete (typed .jobs delete N; remove and del still accepted), and a top-level button shows only where it has a job to act on.

Cache

  • cache_seconds default is now 6 hours (was 12); the yahoo_warm system job runs every 4 hours (was 8) to stay inside it.

CI and packaging

  • actions/checkout v4 to v5 and actions/setup-python v5 to v6, which run on Node 24, to clear "Node.js 20 is deprecated".
  • finbot.service: the wrapped comments are one short line each.

Other

  • Retired telegramChatID, slackChannelID, discordChannelID; calendar home-market fallback; .performance sorted by name; log_width for chat lines; log lines read Telegram @user text; charts attach again to long reports (Telegram's caption counting); docs for all of the above.

Testing

  • 548 tests pass locally (.venv/bin/python -m unittest discover -s tests -b), after merging origin/main.
  • Not verified against live Telegram, Slack or Discord, or live Yahoo/Sharesight.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX

claude added 30 commits October 16, 2026 02:40
…hats

Each chat has had its own jobs since jobs per chat, which left the home chats
two things, the errors that belong to no chat and the first jobs. Both go:
- An error that belongs to no chat (a system job's, a webhook request's, a
  pulled update's) is logged and posted nowhere; one that belongs to a chat
  still goes there. No more "Failed to report error: nowhere to post".
- A report runs for a chat, the one that asked or whose job it is: without one
  it raises "nowhere to post" (delivery.targets; default_chats is gone).
- The three keys are retired: accepted in finbot.env with a note, at start and
  from check_config, to delete the line; gone from the template, Env and the
  startup warnings.
- An upgrade from before jobs per chat still needs them once: lib/migrate.py
  reads them from the environment, for that alone, to give those chats the old
  schedule.cron, watchlist, plans and records.
- util/send_stdin.py takes the chat's key:
  echo hi | util/send_stdin.py telegram:-1001234567890
- The tests run scheduled reports for a chat (common.scheduled()), and
  record_outputs gives a tree without home chats each network's chat.

util/record_outputs.py: 268 steps, identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s own

ARM and CLBT are held on US markets, so their own details come first, as
before. Only when those have no earnings or ex-dividend date does the calendar
try another listing of the company, where the date may be: the one
finbot_adr.json names, the NZX's for an ASX ticker reporting in NZD, or, again,
the home market's for a foreign company's US listing (markets.YAHOO_COUNTRY:
ASML.AS for ASML). The last two are guesses: one that Yahoo does not know
(ARM.L, CLBT.TA: there are none) is logged at DEBUG, not WARN
(yahoo.fetch_detail(guess=True)), and the stock left out. Only the earnings and
ex-dividend calendar (/earnings, /dividend and the scheduled cal job) uses
these fallbacks.

.exdividend and .ex-dividend are .dividend; they were taken as a ticker.

util/record_outputs.py: 268 steps, identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…rt, slash commands first

- Pull mode is the default, and HTTP mode the alternative for those who prefer
  to run an HTTPS gateway; the docs no longer say "no server": finbot runs all
  the same, it only makes outgoing connections. Each guide's table says
  whether finbot listens on the internet.
- Discord's gateway is its gateway API, and the mode gateway API mode.
- README: the new features list; beside Docker's quick start, one with Python
  (git clone and a venv, on Linux, macOS or WSL), both installing every
  optional library; Issues in place of Suggestions.
- Slash commands come first (/price, /jobs add), the dot form (.price) the
  plain message, quicker to type on a phone; the command list and each
  report's examples are slash commands, without the @botName copies.
- No home chats: the guides lose the chat id steps, each chat schedules its
  own reports with /jobs add, and configuration.md lists the retired keys.
- install.md: a Python venv anywhere else, and what Docker, a venv and a
  Debian host each install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A schedule is which days (daily, weekdays, some weekdays, a day of some
months, or a date once), which times on them (a time of day, or a repeat
within a window of hours), or a span across days such as Mon 9am to Sat
3pm, which cron cannot hold as one line; and the zone it is in.

- parse() reads the English a chat types ("weekly at 9am", "hourly at :01
  weekdays from 10am to 9pm", "every 31 mar", "on 28 oct 2026"), with a
  zone last as a name, a place or what people call it (EST, AEST).
- describe() says it back by its zone's clock, with the local time beside
  it where the clocks differ.
- next_after() gives the next run: a skipped time runs at the end of the
  gap, a repeated one runs once.

Nothing uses it yet; the scheduler moves onto it next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…jobs

Each chat's jobs now run on schedules of finbot's own (lib/when.py) and
whitelisted actions; no cron line is shown, typed or stored, and croniter
is gone.

- Default jobs (lib/catalog.py): 35 items, every one switched on in each
  chat finbot knows. That is every chat in the old finbot_schedule.json,
  every old home chat, and any chat at its first command. Its old cron
  jobs are not carried over.
  - Trades runs as one span, Monday 9am to Saturday 3pm Sydney.
  - Market reports run in their market's zone.
  - Performance and price cover the days between their runs.
- Message jobs replace the reminder report and reminder.json: their texts
  are default jobs now, AU ones only where country_code is AU.
- A job is an action from a fixed table of report modules, with arguments
  its own parser takes. A stored job naming anything else is logged and
  left out, never run.
- .jobs lists the jobs a page at a time, with buttons: Enable/disable,
  Delete (jobs added here only), Add (report, how often, day, hour; Back
  and Cancel), Run now and Next. A stale or forged press acts on nothing.
- .jobs add takes English ("price close weekdays at 4:30pm New York",
  "message every 31 mar "text""); .jobs next and .jobs system are new.
- Each list marks a job that has not run yet with *; a one-off past its
  time is deleted as the list is shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…eminder.json

- doc/scheduling.md is rewritten:
  - the menu and what to type;
  - schedules in English, with zones and daylight saving;
  - message jobs;
  - the 35 default jobs as a Melbourne chat sees them;
  - the whitelist of actions;
  - what an upgrade leaves behind (finbot_schedule.json's cron jobs,
    reminder.json).
- The job examples in commands.md now use English.
- configuration.md: the country_code and timezone rows, and reminder.json.
- install.md drops croniter; docker.md drops reminder.json.
- The network guides describe the menu's buttons and say every chat has
  the default jobs.
- CLAUDE.md maps lib/when.py, lib/catalog.py, the new lib/jobs.py, the
  message report and the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
- In every list, a default job's name carries †, in place of the
  Delete screen's "(default)". Each page ends with a key to the marks
  it shows: "* not run yet, † default (no delete, only disable)", or
  whichever half applies.
- Enable/disable has an All button:
  - "All off" when every job of the chat is on, switching them all off;
  - otherwise "All on", switching them all on.
  It says what it did, and a press drawn for a list that has since
  changed switches nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…r log

Add:
- Every frequency's screens go down to the minute:
  - one-off: the date, the hour, the minute;
  - daily and weekdays: the hour, the minute;
  - weekly: the day of the week, the hour, the minute;
  - monthly, quarterly and bi-annual: the day of the month, the hour, the
    minute (two pages on Discord);
  - annual: the month, the day, the hour, the minute;
  - repeating: the interval, then the days.
- Each screen marks its default with † and says "† indicates default".
  Nothing is skipped.

Direct messages:
- A direct message with the bot starts with its default jobs off. That is
  a person's chat on Telegram, a D channel on Slack, and on Discord a
  message from no server, which its updates now say (inbound.Message.dm).
- Once, at the next start, every job of each direct message chat finbot
  knows is switched off. Discord's are found by asking Discord, and the
  old schedule file's DMs are made known with their jobs off. It is
  recorded in finbot_upgrades.json, so it runs at one start only, and is
  tried again at the next start if Discord cannot be reached.

Log:
- These go to DEBUG: a button press, its acknowledgement, the message
  edited in place (logged='debug' on every network), and a success
  answered to a network.
- The tickers asked of Yahoo for trades are logged sorted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…rts by name

Enable/disable, Delete, Run now, Next and Add's first screen say Cancel, back to the
list. Add's report buttons drop their numbers ("price", not "5 price"), and its message
no longer lists them; Add's later screens keep Back, a step back, beside Cancel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The menu's Next button reads Upcoming, its screen "Upcoming runs:", and the typed
form is .jobs upcoming (help, autocomplete, the slash command's description and the
docs); .jobs next still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A request's log line (its method and URL) was cut at a fixed 73 bytes. The
log_width preference now sets it, in characters, for any line that can run long:
log.cut() keeps a line within it, its last character an ellipsis, and 0 cuts
none. The default, 71, gives the same lines as before, and none longer (a line of
72 or 73 characters was shown whole).

In Prefs, defaults/preferences.conf and doc/configuration.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
when.cycle(start, count) gives a cycle's months from its start month (quarterly
from Feb: Feb/May/Aug/Nov), and month_kind() reads any such cycle as quarterly or
bi-annual, so Schedule.frequency, days_words ("quarterly, 15 Feb/May/Aug/Nov",
"bi-annual, 1 Mar and 1 Sep") and the period of performance and price follow it.
Typed: "quarterly from feb on the 15th", "bi-annual starting in march", "annual
from aug"; a bare quarterly or bi-annual still starts in January.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…, repeats to the minute

- How often: one-off, hourly, daily, weekly, monthly, quarterly, bi-annual, annual,
  every... ("weekdays" moves under every... as "weekday", hourly out of it).
- one-off: year (this one and the nine after), month, day (from today, tomorrow
  the default), hour, minute.
- quarterly, bi-annual and annual: "start month?", then the day of the month (an
  annual's only the days its month has), hour and minute.
- hourly and every 5 mins ... 12 hours: which days, start hour, start minute, then
  the end time among the runs after the start, or the end of the day.
- every... weekday: hour and minute.
- Each level's choices come from one place (offered), which also checks a press:
  a choice no screen offered (forged, or an older finbot's) is no step of Add.
- "† indicates default" is in italics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…bs like any other

- The top level is Add, Remove, Enable, Disable, Run now and Upcoming..., two to a row,
  and the list ends with how Add is typed, in italics.
- Enable has a button for each job that is off and Enable all; Disable for each one
  that is on and Disable all; each says what it did ("Enabled 3. trades"), and with
  nothing to switch the screen says so. A press switches one way only, so a stale one
  cannot flip a job back.
- Remove has a button for every job, the default ones too, and Remove all, which asks
  "Remove all N jobs here?" first. A removed default job is kept in the chat's record
  ('deleted') and comes back when the same job is added again.
- No "Default jobs:"/"Added here:" headings, no † on job names; the key is "* not run
  yet" alone.
- Typed: .jobs enable|disable|remove|run NUMBER (toggle and del still work); alone,
  each draws its screen. /jobs suggests the new words, the numbers of jobs off after
  enable and on after disable.
- A slash command's description takes the usage lines that fit Discord's 100
  characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The command keyboard of a direct message has .schedule (the jobs) where .beta was,
sorted with the rest; its version changes, so each DM gets the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
- sharesight.configured(): whether its keys are in finbot.env (yahoo_warm and
  sharesight_warm ask it too).
- Without them, sharesight.tickers() is empty, so a chat's reports cover its
  watchlist alone and Sharesight is never asked; sharesight.portfolios() raises
  NotSetUp, an answer, so .holdings, .trades, .performance, .who and .pie say
  "Sharesight is not set up: its keys go in finbot.env (doc/configuration.md)".
- The trades and performance jobs are default jobs only with Sharesight; a chat
  cannot add a job that reports on it, and Add does not offer one.
- README: "If you don't have Sharesight, you can still add your holdings manually
  via the watchlist"; doc/configuration.md says what changes without it.

Before, every report that covers the watchlist failed without Sharesight's keys,
and the trades job posted an error every 20 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… buy & sell, history, compare...

A job may run any command of .help (worker.COMMANDS, the table .help is made from)
but those that change something (watchlist, plan, jobs; Command.job false) and
those a scheduled report already runs (price, performance, trades, shorts,
earnings/dividend, session/premarket). That leaves holdings, pie, who, marketcap,
stock, beta, compare, history, buy/sell, pe, forward pe and peg; a new command is a
job too unless it says otherwise.

- worker.CommandJob stands in for a report module: its usage is what it takes,
  job() reads its words (none where it takes none, at least one SYMBOL or
  PORTFOLIO where it needs one), and run() answers in the job's chat as the
  command would, through a Request aimed at that chat (Request takes a dest).
  Nothing is evaluated: the job names a command of the table, its arguments stay
  words, and a stored job naming anything else is still left out.
- jobs.actions() is the reports and the commands; .jobs add takes a command by
  its name, two-word ones too (forward pe), the words before the schedule
  (".jobs add pe top weekly", ".jobs add compare AAPL MSFT 1y weekly at 9am");
  another of its names (fpe, a typo) stands for it, but buy, sell or both say which.
- Add lists the commands after the reports (buy & sell as one); one that needs a
  stock or a portfolio says how to type it. On Discord the list comes on two pages.
- .jobs add alone lists what each takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
"schedule: 85 jobs of 1 group chat and 1 DM, and 3 system jobs". A chat's record
marks a direct message ('dm': True) from when finbot first knows it; a one-off
upgrade (dm flag) marks the DMs it knew already, Telegram's and Slack's by key,
Discord's as Discord says, switching nothing. A DM's record kept 'defaults': 'off'
only until All on, and All off set it in a group too, so it could not tell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… Sharesight needed

- doc/scheduling.md: the list with how Add is typed under it; the six buttons (Add,
  Remove, Enable, Disable, Run now, Upcoming...) with Remove all, Enable all and
  Disable all; Add step by step, a table of every frequency and its levels down to
  the minute; typed enable/disable/remove; quarterly and bi-annual from a start
  month; the commands of /help a job can run; a removed default job comes back when
  added again; trades and performance only with Sharesight; the DM mark.
- doc/commands.md: the /jobs lines.
- CLAUDE.md: the code map (job_commands and CommandJob, actions(), 'deleted' and
  'dm', delete_all and switch, when.cycle, mark_dms, sharesight.configured and
  NotSetUp) and a new command's job=False.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…legram counts it

Scheduled price reports (close, midsession) went out as text without
their chart. Telegram counts a caption's 1,024 characters in UTF-16 code
units, after its HTML is read: each 🔼 or 🔻 counts 2. finbot counted
code points, so a list of about thirty moves, cut to 1,012 of them, came
to about 1,042 by Telegram's count. Telegram refused the chart and the
report went as text. The shorter names (30 characters) fit more lines,
and so more emoji, into a caption, which is why it showed up now.

- dialects.Telegram.visible() counts as Telegram does: the text the HTML
  shows (no tags, &amp; one character), in UTF-16 code units.
- deliver(): a chart the network refuses goes once more with its heading
  (the first line) alone as its caption and its buttons, and the report
  follows as text. If that is refused too, the report goes as text alone,
  as before. A chart that timed out (it may have arrived) is not sent
  again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A chat message's log line ("[Telegram]: @rob /price ...") went out as
it was typed, over as many lines as the message had, at any length.

- log.cut() puts a line on one line (each run of line breaks and spaces
  one space), then cuts it to log_width as before. At log_width=0 the
  line is whole, still on one line.
- The message line and the line for a message sent twice within a second
  (now "sent it again within 1 s, not run again: TEXT", the text last so
  a cut takes only the text) go through it, as each request's line does.
- The log_width docs (preferences.conf, configuration.md, the Prefs
  comment) name chat messages too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The list and its chart were sorted by gain, best first, so a portfolio's
place changed from one report to the next. Now the portfolios come by
name (ignoring case), then the S&P 500 and NASDAQ 100 benchmarks, in the
same order in the list and the chart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…sector

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…lden image

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ke the height it freed

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ings .history .compare

A picker is one message edited as buttons are pressed (a 1x1 image where the
answer is a chart), .compare ticks up to 6 tickers with an alert on a seventh,
.treemap toggles Sector/Industry, tickers sit behind a sub menu. The Telegram
keyboard now has .compare, .pie and .treemap in place of .dividend, .marketcap
and .pe.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…Group by after, buttons kept under the answer

A picker's buttons stay under its answer (the item shown marked), so another
item, the Sector/Industry toggle or a period redoes it in place; a Slack chart
message keeps its button rows; the log prefix reads 'Telegram @user' not
'[Telegram]: @user'.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
claude added 11 commits October 16, 2026 15:01
… it has a job to act on

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…d keep the picker under it

The first portfolio (pie, treemap), the first ticker (history) or the first two
(compare), alphabetically, with the picker's buttons under the chart; a press
redoes the answer in place from what is shown; the page buttons are only under
the tickers menu; a pie or treemap draws its other charts in a background thread
to fill the cache. The 1x1 placeholder image is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…are gets the tickers menu

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…tay inside it

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s share

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…the pie a little smaller to make room

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… deprecated)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
@robdevops robdevops changed the title Treemap, bare-command pickers, jobs menu and schedules, background chart priming, 6 h cache Treemap, bare-command pickers, jobs menu and schedules, background chart priming, 6 h cache, CI on Node 24 Oct 16, 2026
@robdevops
robdevops merged commit 1b08e27 into main Oct 16, 2026
8 checks 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.

2 participants