Reference
Changelog
What changed in each version, newest first. The version that the server runs is at the foot of the Help panel in the app.
v2026.11.1
- Diagrams in notes and articles. Write a Mermaid diagram in a fenced
mermaidblock, and the note or the article draws it. A note draws up to 20 diagrams. - Measures and custom columns are DuckDB SQL. A measure body is one DuckDB SQL aggregate expression, and a custom column is a row-level SQL expression that is computed when the snapshot is saved. The formula field has a list of common functions and a link to DuckDB's documentation. Your existing measures change to SQL when the dashboard opens, and in the Findings panel you see each one to check, such as a
firstorlastthat takes a column to order by. - Every chart is computed in DuckDB, at any row count. A chart's grouping and totals are calculated before Vega draws the chart, on small datasources too.
- A dashboard with frames opens on Pages. A reader opens a dashboard with a frame shown to viewers on its first page. In Dashboard settings, an author can set the dashboard to open on the canvas. On a phone, such a dashboard always opens on Pages, and the page tabs have a line of their own there.
- The app opens on your dashboard list. Without a link, you land on the list of your dashboards, not on a blank canvas. On the list and on the canvas, you see a band with an offer to connect an AI agent until you connect one or select Don't show again.
- Property sheets in the last editors. Article cards, the theme picker, Dashboard settings and the datasource editor use the same rows and pickers as the rest of the app. Each theme in the picker has a strip of its colors and a mark for its light and dark faces. A send's schedule reads as one sentence with parts that you change.
- A loading view. While a dashboard opens, you see a stack of paper sheets that draw charts.
- Sends carry their own filters. A delivery and a subscription each hold one set of filter values. Subscribe starts from the filters on screen. A changed value on a delivery pauses it until an analyst approves it again.
- Deliveries that follow the numbers, and alerts. A delivery's text can branch with
{#if},{else if}and{else}, so one delivery says "up" one week and "down" the next. A condition can skip a send. A delivery can wait for new data, inside a window of days and hours, in place of a fixed time. An alert sends once when its condition turns true, then a reminder every few hours, then a resolved message when it clears. In the send form, you see the filled text and whether the condition holds now. - Subscribe to your own sandbox charts. A subscription can include charts, scorecards and tables from your own sandbox. When the editors turn sandboxes off, these blocks leave the send, and the send has the reason.
- Agents see the tables you saved with your own Google. In Browse tables, tick a dataset to save its table and column names for your agents. You see and remove the saved tables in Settings → Tables for agents. The copy expires 30 days after your last save.
- Org installs have members and guests. In Administration → Access, an admin names the member domains and other members. Everyone else the sign-in allowlist admits is a guest: a guest sees only what someone shares, never opens anything to everyone and never publishes. The Share panel reads "Everyone at" and your organization's name. An admin can narrow one domain to listed addresses.
- A service account can run from a key file. For an organization whose people Google cannot see, the operator mounts a service account's key file and names it in the service-account registry. In Administration → Credentials, an admin sees the key's path, age and status, and edits who may run as the account.
- Fixed — a delivery set to send after new data could post an edit nobody approved. An editor's change during a refresh now pauses the delivery until an analyst approves it.
- Fixed — a sandbox save could delete the charts your agent added at the same time. A save that meets another writer's change now holds both changes. A first save that holds rows no longer fails, and when a save fails, you are asked before you leave the sandbox, and your work opens again with it.
- Fixed — a save that was still on its way when you switched dashboards and back could undo your edits.
- Fixed — an editor's adopt of a sandbox chart could land with rows that did not match. The rows are now recorded in the same write as the dashboard.
- Fixed — Send now could post the old message while your edit was still unsaved. Send now first saves your edit, and nothing goes out when the save fails.
- Fixed — a scheduled subscription could reach a person whose access was just removed. Each send now checks the dashboard as stored at that moment.
- Fixed — an alert's follow-up went to the old channel after the destination changed. It now posts in the delivery's own channel.
- Fixed — the subscription panel could show another dashboard's subscription after a slow save.
- Fixed — after Revoke all access or Delete account, an agent could get a new grant, and saved agent tables or a profile could come back. Writes that were already on their way are now refused.
- Fixed — a stale save of the Access settings could undo another admin's change. The page now loads the stored settings again, and you make the change again.
- Fixed — Delete account left the address in the member list and in narrowed domains, and a dashboard or sandbox created during the deletion survived it.
- Fixed — a guest's refused change to sharing read as lost edit access. The message now reads what was refused, and that your other changes save once the sharing is back as it was.
- Fixed — a custom column could disagree with its definition after quick edits.
- Fixed — a picked cadence could reset the send time to 09:00.
- Fixed — long dashboards, large patches and many uploads at once could stall the service. A dashboard holds at most 500 datasources and 1,000 blocks, an agent's patch can add at most 40 MiB, one person reads one upload at a time, and one address holds at most 20 live connections.
- Fixed — product text that ended at a failure. About 170 messages, labels and help lines now end with what fixes the situation, in sentence case and with one verb for each act.
- Agents — work a large dashboard a page at a time.
get_dashboardandrender_dashboardtake aframe, andrender_dashboardstops after 40 seconds with what it could not draw.patch_dashboardtakescopyto copy a theme, datasources or blocks from another dashboard, never their rows, and folds findings of one kind into one line. An author or an agent can mark a finding as understood, with a reason. The catalog tools accept"user"to read the tables a person saved. - Operations — the dashboard format moves to version 6. The service changes each dashboard when it is read: a measure becomes DuckDB SQL. A dashboard saved by this release does not open in v2026.11.0, so to roll back, restore the state from a backup taken before the update.
- Operations — key-file service accounts. An entry in the service-account registry file can name a
keyFile, a key the operator mounts outside the state directory. The service refuses to start with a key inside the state directory, with a key it cannot read, and with any key on a public deployment. The install guide has the steps. - Operations — new limits. The limits in the Fixed item about stalls are built into the service. A script that writes the Access settings through
PUT /api/admin/policymust first read them and send the version it read inIf-Match. - Privacy — three changes for the instance's privacy policy. The service stores the table and column names that a person saves for agents, for 30 days after the last save. The environment secrets can include a service account's key file. Before sign-in, a visitor's answer to the prompt to connect an agent is stored in the browser.
- Licence — the document format version moves from 5 to 6. Where a licence agreement names the format version an update migrates from, that version is 6 from this release.
v2026.11.0
- Point and density maps. You can now draw a map of dots or bubbles from a latitude and a longitude, or from a city name. A city name is matched against a list of world places that is part of the app. A density map has cells that the query bins in SQL. Click a map to enter it, then zoom and pan it with the wheel, a drag or a pinch. An editor keeps the current view as the map's starting frame.
- Editors build charts in their own sandbox. The header switch reads View, Edit and Sandbox. In the sandbox an editor tries a chart that viewers do not see, then puts it on the dashboard with Add to dashboard or Replace. The dashboard's sandbox setting now reads "Let people build their own charts in a sandbox", and it covers editors and viewers alike.
- Starter charts. On an empty canvas that has data, you see up to five charts made from that data, as small live previews. Tick the ones you want and place them with one button. A number column with a name such as Customer ID counts as an identifier, so no starter chart sums it.
- Put a chart into an article. With Add to article… in a chart's ⋯ menu, you add the chart, with the filters on screen, to an article you pick or to a new one. Turn on an article's Draft switch to leave the article out of the published copy. The publish question has the names of the drafts that stay out.
- A rich tooltip, when you turn it on. Turn on Rich tooltip in a chart's Tooltip settings. On a line over dates, the tooltip then has a line for every series at the nearest date, with its change, and a total. On a bar, the tooltip also has the share of the total, the rank and the distance from the average. Move the pointer into the tooltip for Keep only, Exclude and Copy values.
- An image link for a published chart. With Copy image link in a published chart's download menu, you copy a picture address to paste into Slack, a README, an email or a slide. The address stays the same when you update the published copy.
- Every import is a stored file. A file you pick, drop or paste is kept as a file, and each datasource is a query over it, so a workbook feeds several datasources that run again. DuckDB reads an
.xlsxworkbook directly, and the import reads a large workbook in the background, one sheet at a time, so the page never freezes. An.xlsor.odsfile is refused, and the message has the steps to save it as.xlsx. Before you sign in, your files stay in this browser. They leave it 7 days after the last use, and at sign-out. - DuckDB SQL editing. In every SQL editor, press Cmd Enter, or Ctrl Enter, to run the query without saving it. The DuckDB editors color DuckDB's own keywords and functions, and link to a new help section on DuckDB SQL.
- See what a BigQuery refresh will read, before you press Refresh. In the Data workspace, you see how much data the next refresh of each BigQuery datasource scans. BigQuery makes this estimate with a dry run, which reads no data and costs nothing. A line under the header has the sum of the estimates for Refresh all. For a scan larger than the server allows, you get a warning before the refresh is refused.
- One timeline for the refresh and every send. In Scheduled sends, the dashboard's refreshes, each delivery and your own subscription are on one time axis over a day, a week or a month. A send that goes out shortly before a refresh is amber, with the fix next to it.
- Approve a changed delivery without sending it. After an edit, a delivery stays paused until an analyst approves it. With Confirm changes, the analyst previews the message and what changed since the last approval, and approves the delivery with no post. The delivery then goes out at its next scheduled time.
- Slack and email messages are laid out. A delivery or an inbox message in Slack has a header, the data's age, the text, an Open button and a footer. Inbox messages group by dashboard, and the messages about an act you have to take come first. A delivery can hold scorecards. A send has no CSV file attached: a table has a link to the dashboard for the rest of its rows.
- Answer an access request from Slack. The Slack message about an access request has Grant and Decline buttons. A click counts only for the person whose address Slack holds for that member. After the click, the message holds the name of the person who answered.
- Clean view. Turn on Clean view in the View options bar to hide the filter chip, the chart options, the comment count and the problem mark until the pointer or the keyboard reaches a block. The setting holds for every dashboard in this browser.
- A button whose act is still running is marked. After 300 ms, a spinner covers the button's label, and a second click does nothing until the act ends.
- A rate's change is in percentage points. A rate that moves from 40% to 35% now reads −5 pp, and "both" reads −5 pp (−12.5%), on scorecards, tables and sends.
- Scheduled refreshes that are due together finish sooner. The server starts up to 10 BigQuery queries ahead of the one it reads.
- Admins choose which agent apps connect. In Administration → Access → Agent apps, you admit any app, or only the apps you name: Claude, ChatGPT, other hosts you type, and agents on a person's own computer. You cannot admit a company account of an app and refuse a personal one. The agent setup tabs on the landing page and in Settings are only for the apps the setting admits. An agent that the setting refuses is marked as stopped in Settings, Users and Usage.
- Admins arrange the landing page. In Administration → Pages, you hide and order the agent setup tabs, add a tab of your own, and turn the landing page's story and features on or off.
- In Errors, you read what failed in an agent call. Each row of Administration → Errors has the kind, a refusal or a fault, then the reason in plain words, the tool, the fix, and the person, the agent app and the dashboard. Turn on Faults only to hide the refusals. Usage has a count of calls per agent app.
- Fixed — a schedule set in the hour that the clocks change fired twice, or a day late. A send at a fixed time in the hour the clocks go back now fires once. A schedule in the short hour of a change that moves the clocks by less than an hour now fires on time.
- Fixed — a downloaded CSV file could open a cell as a formula in Excel or Sheets. A text cell that starts with =, +, -, @, a tab or a line break now opens as text.
- Fixed — after a restart of the service, a passkey answer could be used to sign in a second time. Each passkey answer now signs in once only.
- Fixed — a new block landed on top of another block. It now takes the free spot nearest the middle of the view, and the canvas glides to it.
- Fixed — a sideways trackpad swipe over a popover or the code editor left the dashboard. The canvas, the Chart workspace and the Data workspace no longer take the browser's Back gesture.
- Fixed — an untitled block had its id as its title. Its header now has no text, and an untitled section or frame reads "Untitled section" to an editor only.
- Fixed — a column whose name holds a dot or a bracket drew an empty chart. It now draws and validates.
- Fixed — a taken-down published link opened as raw text. It now opens a page with the text that the link holds nothing.
- Fixed — library pages scrolled only over their middle column, and a wide table hid its last columns. The whole page scrolls, and tables fit the page from 800 px up.
- Fixed — on a phone, a fitted dashboard ran under the icon rail. In the same UI review, we also fixed the phone header, the date range in the Controls panel and many smaller layout faults.
- Fixed — on a phone, the top bar hid Administration, and the list pages ran past the screen. At phone width the section tabs have a row of their own. The lists of dashboards, charts and datasources have the name, the date and the row menu, and the Administration areas wrap, so you see the one you are in.
- Fixed — a selected chart's editing parts could sit away from the chart, over the charts beside it. After a scroll, and on a chart at the right edge of a row, you now see the fields, the marks and the data strip at their own chart, inside the window.
- Fixed — in the Controls panel, Show on canvas looked like plain text. It is a button under its field.
- Fixed — the zoom bar covered the blocks under it. The bar now has a band of its own under the dashboard, so no block passes under it.
- Fixed — a scorecard in a small block cut its number. The number now shrinks until it fits the block.
- Fixed — the Currency setting in Dashboard settings looked empty. It now has the name of the current choice, such as "Dollar sign ($)".
- Agents — fewer calls and less to read on a large dashboard.
run_datasource { datasources }runs up to 20 datasources in one call, inputs before the views that read them.render_block { blocks }renders up to 12 blocks.create_dashboard { from }copies a dashboard with its data, when the person may take its rows under the rows setting.patch_dashboardreturns the findings that the patch added and fixed, with a count of the rest. The outline is shorter. It has the image address of each published chart, and for each chart over 5,000 rows, whether the browser reduces its rows or what in the spec stops it. An editor can usepatch_sandbox. The refusal of an agent app has the codeagent-app-not-allowed. - Operations — the state database moves to schema version 7. The service upgrades it at the first boot and saves the copy from before as
state.db.before-v6. The second start deletes that copy, so decide on a rollback before you restart. To roll back to v2026.10.7, stop the stack, put the copy in place ofstate.db, and setARKUSH_IMAGEback to the previous tag. The install guide has the steps. - Operations — the landing page's story and features are off by default. Turn each one on in Administration → Pages.
- Operations — a chart render reserves 1.25 GiB of the memory budget, up from 512 MiB. With the compose default of 4 GiB, the service runs two renders at once, or one query and one render. If sends or
render_blockcalls stay in a queue or get a server-busy answer, raisemem_limit. - Operations — the Slack buttons work only with the Slack app's signing secret. To let owners answer access requests from Slack, set
ARKUSH_SLACK_SIGNING_SECRET, orARKUSH_SLACK_SIGNING_SECRET_FILE, and set the Slack app's interactivity address to/slack/interactionson the instance. If an identity-aware proxy is in front of the service, let/slack/interactionsthrough it without sign-in. The install guide has the steps. - Privacy — two changes for the instance's privacy policy. The Slack signing secret sits with the environment secrets. Deliveries and subscriptions no longer attach CSV files.
v2026.10.7
- Find anything, and run any command, with ⌘K. Press ⌘K, or Ctrl K, on any page to find a dashboard or a datasource by the words inside it: a title, a note, a chart's axis, a column or a warehouse table. The search never reads the rows. Each row shows its owner, its last edit and the official mark, so you can tell two copies apart. The same list runs about 40 of the app's commands, such as Refresh data or Add a chart, and shows each command's key. Press ? to see every command. The Dashboards page and the datasource library find the same words.
- The dashboard list opens on For you. It shows your own dashboards, the ones shared with you, the ones you opened in the last 30 days, and the dashboards open to everyone that carry a Ready or Official stamp. All you can open lists the rest. Who can open a dashboard did not change. After sign-in with no page to go back to, you land on the dashboard list. Every list page has one toolbar, and you sort from the column headings.
- Facts read at a glance. A status, a kind, a count, a size or an age shows as a glyph and a number. The words are on hover. The header's freshness chip is a clock and the data's age.
- Refresh all shows its progress, with one mark per kind of source and the count left. If you open another dashboard while a refresh runs, the app asks first.
- See which columns nobody described. Each datasource that ran shows whether its columns are documented, partly documented or undocumented, in the Data workspace and in the datasource library. A description from the warehouse counts.
- Usage counts for the admins of an org install. When the operator turns usage analytics on, Administration → Usage shows how many people use the instance, how they use it, which dashboards they read and which refreshes cost most. It shows counts and never names. A count of fewer than 3 people shows as "<3". Users gets a Last active column, and a dashboard's owner sees its readers this week. An admin can build dashboards on the counts with the Instance usage source.
- One Slack bot per instance. An admin adds a Slack destination by picking a channel, with no token in the form. Deliveries shows the bot's workspace and scopes. Subscriptions with no Slack channel marked go to each person's Slack direct messages.
- Fixed — a refresh that was still running when you opened another dashboard wrote its rows into the new one. A refresh now stops when its dashboard closes.
- Fixed — on a touch screen, a table caught the finger that meant to pan the dashboard, and a chart's tooltip needed an exact tap. A finger now pans from anywhere on a block in view mode. A tap shows the nearest tooltip, above the finger.
- Fixed — Findings flagged correct charts. A calendar heatmap, a stack order, a date over time buckets and a dash beside a color no longer get a finding. Each finding shows once per block.
- Agents — the list tools search, and list For you first.
list_dashboardsanswers the For you list by default, andscope: "all"lists every dashboard the person can open.list_dashboardsandlist_shared_datasourcestake aqueryof words, and answer how many matched. A dashboard outline, a datasource run, a preview and a shared datasource name the columns nobody described, and tell the agent to warn its person and offer descriptions. - Operations — one Slack bot token. Set
ARKUSH_SLACK_BOT_TOKEN, orARKUSH_SLACK_BOT_TOKEN_FILE, to the token of the instance's Slack bot. A Slack destination that holds its own token works until 2026-12-01, and the service logs a boot event for it. Open each one in Deliveries and save it to move it onto the bot. The install guide has the steps. - Operations — usage analytics. Set
ARKUSH_USAGE_MONTHSto a number of whole months, from 1 to 120, to turn usage analytics on. Tell the organization's people first. Remove the variable to turn it off, and the service then deletes the record./admin/usageis the new admin page, and an identity proxy in front of the service must let it through where anonymous use is allowed. - Operations — do not set a Node heap flag. A
--max-old-space-sizeflag, set inNODE_OPTIONSor elsewhere, also lifts the memory cap of each chart render. The service now logsrender_heap_uncappedat boot when it finds one. - Privacy — three changes for the instance's privacy policy. With usage analytics on, the service keeps counts of each person's acts per hour, under a code in place of the address, for the months the operator sets. Each person's Settings → Your data says so. The service keeps the dashboards each person opened in the app for 30 days, for the For you list. The Slack token leaves the destinations file and its backups, and sits with the environment secrets.
v2026.10.6
- Ask for access. A link to a dashboard, a shared datasource or a file that is not shared with you now lets you ask its owner for view access, with a note. A viewer asks for edit access in the Share dialog. The owner hears in the inbox and by email, and grants or declines above the sharing form. When the owner cannot act, editors and then admins hear instead. You never learn who the owner is. After a decline, you can ask again in 7 days.
- Search a dashboard. ⌘F opens a Search panel that finds text in chart marks, in every page of a table and in charts not drawn yet. A click on a match opens its page and centers the block. Press ⌘F a second time for the browser's own find.
- Link to one block. Add
?block=and a block's id to a dashboard's address, and the dashboard opens on that block's page, centered on the block.?chart=opens a chart full-window. - Compare periods and switch the time grain. A named period, such as this month to date, compares with the same days of the period before. A Dates picker and the chart options switch a chart between day, week, month and year. A summed table shows the change from the earlier period, and a trend line in each row.
- A chart in a table row's tooltip. Point at a table row, and the tooltip draws a chart filtered to that row.
- Hide a block from viewers. An editor keeps a block on the canvas, and viewers, published pages and scheduled sends leave it out.
- Edit every block type on the canvas. Controls, notes, sankeys, images, frames, shapes, sections and connectors each get their own editing card in place of a side form. You write a note in place, with a format bar. You pick what a control applies to on the canvas.
- Shapes draw as bare figures, with no card or header, as in a drawing tool. A double-click edits the label.
- One form for scheduled sends. Deliveries and subscriptions share one Scheduled sends panel and one form, with schedule presets and a time of day. Charts and tables group by frame, with a filter, and pointing at one shows it as the send draws it.
- A Fields table and a focused Model view in the Data workspace. One row per column holds its label, type, format and description, with the warehouse's description beside it. A derived view's column shows the description of the column it came from. The Model view centers on one datasource and shows what it reads and what reads it.
- Schedules for admins. Administration → Schedules lists every scheduled refresh and delivery, how it stands and its last 20 runs. Editors see the last runs in the dashboard's Scheduled refresh section. A failed scheduled run retries after 5, 10, 20, 40 and 80 minutes, then waits until a person runs it.
- Refresh when triggered. A dashboard with Refresh when triggered on refreshes when a job calls
POST /refresh/dashboards/<id>orPOST /refresh/datasources/<id>with the instance's refresh token, for example after a warehouse load ends. - Fixed — a large dashboard showed nothing for seconds and froze while you edited. A dashboard now shows at once, each chart draws when its rows arrive, the cheapest charts first, and an edit no longer blocks the page.
- Fixed — a large derived view or file reduction could use most of the server's memory. It now streams, and holds about 1 GB where it held up to 2.7 GB.
- Fixed — a screen reader and the Tab key met blocks in the order they were made. They now meet blocks top to bottom. Each block title is a heading, and keyboard focus brings a block off screen into view.
- Fixed — renaming a measure left the blocks that used it pointing at nothing. A rename now names those blocks, moves them with the measure, and one undo takes it back.
- Fixed — the app stayed blank on a local network address without HTTPS. It now starts.
- Fixed — a control with no title showed its internal id as its header. It now shows no header text, and its widget names the filter.
- Fixed — a block one person deleted while another edited it came back at the wrong stacking place. It now returns directly above the blocks it covered.
- Fixed — two saves to one uploaded file at the same moment could lose one change. Both changes now stay.
- Fixed — a webhook receiver could get the same send twice after a restart. Each post carries an
Idempotency-Key, and one slow refresh no longer holds the sends behind it. - Fixed — Arkush asked Slack again on every run for a person Slack does not know. It now asks once a day, and Settings → Notifications says what to paste.
- Fixed — controls that apply at once were checkboxes. They are switches now. A notice's buttons read as buttons, and empty values in charts and controls are readable.
- Fixed — a script injected into a page could run. Every app page now refuses code built from text and inline scripts that do not carry the page's one-time key. A proxy or CDN that rewrites pages, such as Cloudflare's script or email features, must leave the app's pages alone.
- Agents — start-up text that every agent client reads in full. The instructions an agent gets on connecting are short enough that Claude Code no longer cuts them, and every rule taken out of them is in the authoring guide.
preview_transformtakes a newdashboardsargument and answers one query over up to five dashboards.render_blockanswers the block's link. An agent client finds the endpoint from the instance's bare address, at/.well-known/ai-catalog.json. - Operations — five new public paths.
/.well-known/ai-catalog.jsonand/mcp/server-cardserve the agent catalog./refresh/dashboards/and/refresh/datasources/take the refresh trigger and answer only the refresh token./admin/schedulesis the new admin page. An identity proxy in front of the service must let the first four through always, and/admin/scheduleswhere anonymous use is allowed.robots.txtnow says that search engines and AI assistants may read the public pages and that AI training may not. - Operations — the BigQuery setup script ships in the image at
/app/deploy/setup-bigquery.sh, for the install guide's server-side BigQuery steps. - Privacy — the service keeps two more kinds of record. Access requests: who asked, for what, the note and the answer, for 90 days after the last change. Run history: the last 20 scheduled and triggered runs of each datasource and delivery, with no person in them, deleted with the dashboard. Add both to the instance's privacy policy.
v2026.10.5
- Comments read as a conversation. Each comment shows the writer's initials, name and age, and a Reply field ends the thread. A resolved thread folds to one line. A comment shows (edited) only when its words changed.
- Sharing names whom to ask. A viewer's Share dialog names the owner, the contact and the people who can change sharing first. The other people with access are one click away.
- See what a filter reaches. When you point at a chart mark that filters on a click, Arkush outlines each block that the filter reaches. A click on a date bar keeps its own filter, so the chart keeps the range of the date range control.
- Field pills in the chart editor. A chart setting's popover opens away from the chart, and its field shows as a pill with its aggregate on it. The map and the comparison popovers use the same pill. A popover hint opens with Enter, Space or a click, and Escape closes it.
- Errors for admins. In Administration → Errors, an admin reads the server errors of the last 14 days, newest first, each with the person, a help link and a reference. A message about a server failure ends with that reference. When a person quotes it, the admin finds the line. The Administration index counts the problems of the last 24 hours that still fail. People appear in the stored errors by pseudonym only.
- Admins edit the onboarding questions. In Administration → Pages, an admin adds, rewords and retires the questions new people answer, and the file keeps its comments. A retired question is never asked again, and its answers stay. A question added later goes only to people who arrive after it.
- Storage for admins. In Administration → Storage, an admin reads the free disk space, the bytes each part of the data uses, and each dashboard's share, sorted as the admin picks. The Administration index warns when the disk has less than 10% or less than 2 GiB free.
- Slack reach. Each Slack destination shows which features its bot token reaches. A Slack bot that sends subscriptions needs no channel of its own.
- A warning about the report link. When publishing is on and the instance names no security contact, the Administration index warns that published pages offer no way to report them, and links to Appearance, where an admin sets the contact.
- Fixed — large dashboards were slow and used much memory. A large snapshot loads once from its Parquet file, a chart copies fewer rows, and a BigQuery run reads three result pages at a time. A chart far off screen draws when it comes near the view.
- Fixed — a large file imported into a new dashboard did not save. It now saves.
- Fixed — a firewall that stopped a save read as lost edit access. The message now says that a security filter stopped the save and that the work stays in the browser, so your IT team can let it through.
- Fixed — a refresh that failed on the author's SQL read as a failure outside Arkush. It now reads as an error in the SQL, which the author fixes.
- Fixed — Subscribe on an install behind Cloudflare showed a bare "Bad gateway" page. The message now names what failed.
- Fixed — a Refresh all with many failures filled the screen. The notice names the first three failures, counts the rest, and points to the Data workspace.
- Fixed — a scorecard refused the max or the min of a date column. Max, min, first and last now show a date from the column.
- Fixed — a chart tooltip named one field twice. It now names each field once.
- Fixed — the spinner stood still when the system asked for reduced motion, and the page read as hung. The spinner and the loading bar now keep moving.
- Fixed — the Runs as field disappeared with no reason. It now stays in view, inactive, with the reason and the act that changes it.
- Fixed — an open tab showed old rows after a refresh made elsewhere. It now shows the new rows.
- Fixed — the scheduled pass opened every dashboard. It now opens only the dashboards that are due or have a subscriber.
- Fixed — after a restore, an account deleted under an address with no dot in its host came back. A restore now deletes it again.
- Operations — the image runs on Debian, on Node 24.22.1. Node 24.22.1 carries Node's October security fixes, so take this image on every instance. Hostname lookups now follow the order the host's DNS settings give. The image has no
wget. If your compose file came from an earlier image, copydeploy/docker-compose.ymlfrom the new image, or change its health check to thenodeline in that file. The node user stays uid 1000, and the state volume needs no change. - Operations — the schema moves from version 5 to version 6. The first start stores the next due time of each dashboard, and keeps the database as it was in
state.db.before-v5. To roll back, follow the install guide's rollback steps before the container starts a second time, because the second start deletes the copy. - Operations — an `ARKUSH_` name the service does not read no longer stops the boot. The service logs one
config_unreadevent that lists the names, and starts. A retired name still stops the boot and names its successor. A rollback to v2026.10.4 or earlier still refuses a name that release does not read. - Operations — two new paths, `/admin/errors` and `/admin/storage`. A deployment that allows anonymous use lets them through, as it does
/admin. - Privacy — the service keeps one more kind of record. It keeps a copy of its error lines, for 14 days and at most 5,000 lines, so admins can read them in the app. The lines name people by pseudonym and carry no request body or token. Backups carry the same lines. Add this record and its retention to the instance's privacy policy.
v2026.10.4
- Snapshots of up to 500,000 rows. A BigQuery run on the server streams its result to disk, so a large run holds far less memory. A large snapshot made in your browser goes to the server as a Parquet file. Past 50,000 rows in one chart, the editor still gets advice that the chart is slow.
- Maps. A chart colors countries or regions by a value. Pick Map in the chart types, then the outlines and the column that holds each region's code. World countries, the regions of every country, Ukraine's 27 regions and the US states come with the app. Crimea and Sevastopol are Ukraine in every set. A GeoJSON file you import draws its own outlines.
- See which filters act on a block. The block header says how many filters apply, for example 2 filters. A click lists each filter, its value, and whether it filters this block or skips it and why, with Clear and Show the control. Nothing covers the block's content.
- A calmer sharing list. Each person or group has a Can view or Can edit menu, with Remove as its last item. The newest grant shows first, and from 8 grants the list has a count and a search field.
- See a BigQuery table before you query it. The table browser shows a table's columns, types, descriptions, row count and size, and warns when reading the whole table would pass the scan limit.
- How old is the data. A click on the Data: 2 d ago chip lists each datasource with its age and whether it is behind its schedule, under the chip, with the dashboard still on screen.
- Charts. A dashboard's currency prints its sign wherever a format has
$. A chart can keep its top N categories by its own measure and fold the rest into Other. - Publishing asks first. Before a dashboard goes on the internet, you confirm that anyone can find it and search engines can list it. The parts of a published page that are not the dashboard itself ask search engines not to list them.
- A new home page on arkush.app, written for people who already use an AI assistant. The warning that you have no account moves into the app.
- An audit trail for admins. At the end of Administration → Configuration, an admin reads every admin act and every access change: who shared a dashboard, a file or a shared datasource, who connected an agent, who suspended or deleted an account. Each row names who acted, on what, what changed and when. You filter the rows by person and by act. Sign-ins are not in the trail.
- A time limit for BigQuery queries. An admin sets it in Credentials, from 1 to 60 minutes, 5 by default. A query past the limit is cancelled in BigQuery, so it stops billing, and the message names the limit.
- Destinations in a form. An admin adds and edits Slack, email and webhook destinations one at a time. A saved token or secret reads Set with Replace, and it never comes back to the browser. When a webhook's address changes, you enter its secret again. Edit the file stays for operators, with every secret masked.
- Fixed — charts crashed on large snapshots. A chart with a minimum, a maximum or a scatter summary failed past about 120,000 rows. It now draws.
- Fixed — Refresh all ran derived views before the new rows arrived, so a derived view over a server-run datasource failed with "Table with name … does not exist". The tab now loads its own server results first.
- Fixed — a scheduled delivery could go out twice after a restart. The service now records each send before it starts the next one, and a shutdown waits for the send in progress.
- Fixed — an error read "[object Object]" when a request was cut short. It now says what happened.
- Fixed — a schedule whose time zone was not text ran on UTC. It now stops, as a schedule with an unknown time zone does.
- Fixed — in an agent's picture of a whole dashboard, one chart could cut off the bars of another.
- Fixed — both public sites send the security headers a site checklist named, and an unknown
/.well-known/address answers 404. - Agents — a new tool, `render_dashboard`, draws the whole dashboard as one picture, with the charts in their places, and lists the blocks that overlap. A patch can name a block by its id,
/blocks/@<id>/, andrender_blockanswers a scorecard or a table as the text it prints. Answers are compact JSON, about half the size.get_bigquery_tablealso answers the table's size, and the snapshot cap is 500,000 rows. - Operations — one new optional variable, `ARKUSH_AUDIT_MONTHS`: the whole months the service keeps an audit-trail row, from 1 to 120, 13 by default.
deploy/docker-compose.ymlpasses it through. The service adds the audit table on its first start, with no step from you. A new public path,/boundaries/, serves the map outlines: an identity proxy in front of the service must let it through with no sign-in. A streamed BigQuery snapshot of 500,000 rows fits a host with 3 GB of memory. Derived views, database reads, files and addresses still hold their rows in memory, so keep those runs smaller on such a host (the install guide, Capacity). - Privacy — the service keeps one more kind of record. The audit trail keeps, for 13 months by default, who acted and on whom for each admin act and access change. A person's data download lists their rows, and deleting the account relabels them. Add this record and its retention to the instance's privacy policy.
v2026.10.3
- Your inbox reaches you by email and in Slack. An admin turns on each channel. You then get one message that names the inbox entries you have not read, with a link to each dashboard. In Settings → Notifications, you choose the categories per channel. A message never holds the text of a comment.
- Admins hear when a person leaves. Where a Slack bot sends the subscriptions, once a day the service asks Slack about each person with work that runs in their name: a subscription, a blessed delivery, a scheduled refresh or an agent. When Slack deactivates one of them, every admin gets an Inbox notice that links to the person under Users. The service never suspends anybody on its own.
- A clearer app. Popups stay inside the window, and overlays show above dialogs. The refresh schedule, deliveries and subscriptions share one time-zone list with each zone's offset from UTC. The admin area is easier to move around in. Checkboxes, radio buttons and other controls follow the dashboard's mode and accent colour. Refresh data sits in the dashboard header.
- The datasource library. Each shared datasource has an address that opens its page, for people and agents alike. When you add a shared datasource whose rows no longer match its SQL, the dashboard gets the SQL without the old rows, and you run it to get fresh ones.
- A Google Sheet shows its columns in the Data workspace before you write any SQL.
- Search engines find the public pages. arkush.app and docs.arkush.app each serve a sitemap, and search engines skip the app's own pages. The sign-in page names the terms, and every public page ends with a site footer.
- Fixed — a subscription made while the subscription channel was email stayed on email after an admin moved the channel to Slack. It now finds the person in Slack at its next send.
- Fixed — the setting zones and the Properties form around a block stayed behind during a drag. They now follow the block before the drop.
- Fixed — Google's file dialog opened without its content. It now opens with your files.
- Fixed — the SQL of a Google Sheet or a file got a BigQuery dry run, which is meant for warehouse SQL only. It now gets none.
- Fixed — a schedule made in a browser that runs in UTC stored UTC as its time zone. A new refresh schedule, delivery or subscription now stores no zone there, as picking UTC does. The schedule ran at the same times before and after.
- Operations — a new path, `/sitemap.xml`. An identity proxy in front of the service must let it through with no sign-in, as it does
/robots.txt. Inbox email uses the mail relay that sign-in codes use. Inbox messages in Slack and the departure alert use the bot that serves direct-message subscriptions, with the scopes it already has. Each channel stays off until an admin turns it on. - Privacy — the service keeps one more kind of record. For each person it asks Slack about, it keeps their Slack member id and whether Slack deactivated them. Add this record to the instance's privacy policy.
v2026.10.2
- Fixed — a comment's words stay with the person who wrote it. A save from another person, in the app or by an agent's
patch_dashboard, keeps the text of a comment that person did not write. That person can still resolve, delete or react to it. A new dashboard stores every comment under the person who creates it, and a duplicate keeps the authors and the words of the threads it copies. An agent that acts for you reads a comment as yours only when you wrote its words.
v2026.10.1
- One chart fills the window. Open in Chart workspace shows one chart with its code, its preview and the rows it draws, side by side. The code saves when you pause typing. Code that does not parse shows an error under the chart that names the line, the column and the fix, and leaving with it posts a notice. Edit spec opens the chart here with the cursor in the code, and the side-panel spec editor is gone. Charts also have their own Charts page, and a new chart can start there.
- The canvas keeps its tools beside the work. Rail panels float over the canvas, close on a click outside, and pin to dock. A selected chart shows its settings in named zones around it, and a selected frame or section shows Settings beside its name.
- A measure shows as a running total, a change, a share, a rank, a percentile or a moving average, chosen on the chart without SQL.
- Bigger data, with advice before it gets slow. A snapshot holds up to 100,000 rows. The editor gets advice past 50,000 rows in one chart and 250,000 rows across a dashboard. A chart over many rows draws from rows the browser reduces first, so a long line or a dense scatter plot stays quick, and an agent's picture of a chart is reduced the same way.
- Deliveries and subscriptions send by email and to a webhook. A webhook request is signed, so the receiver can check it came from this server.
- Dashboards a keyboard and a screen reader can use. Charts and sankeys take focus and read their values aloud the way the chart shows them. Each chart gets a generated summary, and an author can write a description that goes everywhere the block goes. Validate warns when a chart would mislead its reader or is hard to read, and names low contrast.
- Arkush for organizations. A public deployment keeps one waitlist on its own page beside the landing page, and every feature it lacks links there.
- One Settings page for your own account, with Download my data, Delete my account and a username rename. An admin can suspend an account.
- Agents and data. An install writes context for its agents: its tables, its vocabulary and its house rules. A new tool,
list_fonts, names the instance's fonts. A chart takes a distinct count, and a file at an address can be gzip or nested JSON. - The help moves to the root of docs.arkush.app, and the install guide is published as short pages at
https://docs.arkush.app/install/, each with a markdown copy for an agent. - Fixed — blank and empty values read the way a person expects, from the uploaded file to the chart and the filter list.
- Fixed — a chart split into panels by a channel drew only its first panel. It now draws every panel.
- Fixed — a viewer's sandbox refused every edit on a dashboard with an article.
- Fixed — a dashboard with a damaged block entry could not be edited, and an uploaded gzip file read no rows.
- Fixed — a dashboard file that came with a broken block could not be saved after the page reloaded before its first save.
- Fixed — when the server could not read a service account's access policy, the refusal blamed a missing role. It now names the cause Google gave.
- Fixed — a paragraph after the first one in a list item showed its formatting and links as raw text, in notes, articles and the docs.
- Fixed — four advisories in the code editor's bundled sanitizer. The code editor moves to monaco-editor 0.57.0.
- Operations — the schema moves from version 4 to version 5. The first boot adds one column to the sessions table and keeps the database as it was in
state.db.before-v4. To roll back, follow the install guide's rollback steps before the container starts a second time, because the second start deletes the copy. - Operations — the agent context has its own file.
ARKUSH_AGENTS_FILEnames it,/data/pages/agents.mdin the image, and an analyst edits it in the app. Unset outside the image, the agents read that this server wrote no context. - Operations — `ARKUSH_WAITLIST` is retired. A public deployment keeps the waitlist with no setting, and an org install never does. The service still boots with it and logs
config_deprecated. From 2026-11-10 a release may refuse it, so remove it from the env file. - Operations — four new paths. An identity proxy in front of the service must let
/csp-reportthrough with no sign-in: browsers send it reports of the strict page policy, which runs report-only. A deployment that allows anonymous use also lets/charts,/settingsand/organizationsthrough, as it does/app. - Operations — the install guide moved to
https://docs.arkush.app/install/, one page per part. An install's/helplinks now open the help at the root of docs.arkush.app. - Agents — a new tool, `list_fonts`, names the fonts the instance registered, the names a theme takes.
- Privacy — the stored data gained four kinds of record, and the sweep removes two. Add them to the instance's privacy policy. The service keeps the addresses an admin suspended, until the suspension is lifted. It keeps one keyed hash per deleted account, which keeps a deletion applied after a restore. The destinations list holds webhook secrets and email recipients. A public deployment keeps one waitlist entry per person, with the address, the date, the features that brought them and an optional text, and removes it after a year. A passkey unused for 13 months is removed.
v2026.10.0
- A published dashboard ends when the dashboard does. Moving a published dashboard to the trash, an admin closing it to private, and deleting its owner's profile each take the published copy down and free its storage. Restoring from the trash does not publish it again. A dashboard given to a new owner stays published at the same address. Every published page links Report this page to the instance's contact, where the instance names one.
- An examples page. On an instance that publishes, an admin writes an examples page that links published dashboards. Visitors open it at
/examples, and the landing page links it. - The server keeps its heavy work inside its memory. Queries and chart pictures reserve memory before they start. When the server is full, a new one waits a few seconds, then gets a message that the server is busy and to try again shortly, in place of a crash that stops everyone's work.
- The sign-in pages match the rest of the site. The sign-in page, its error pages and the page that connects an agent take the paper look, in light and dark.
- A new mark: a cream sheet in blue ink, in the browser tab and on the home screen.
- Fixed — Sign in asked a signed-in person to sign in again. A person who is already signed in now goes straight to the app. Try signing in again and use a different address still open the sign-in page.
- Fixed — a shape with a label of the wrong type broke the editor, and some shapes did not fill their block.
- Fixed — a server run started from the app could fail while the server was busy. It now waits for its turn and for the run to finish.
- Fixed — a tab that published or unpublished a dashboard showed itself a notice about its own change, and a publish from another tab offered an Undo that could not work.
- Fixed — a branded instance's landing page named the instance as the tool. It now calls Arkush the tool and the instance the place that runs it.
- Fixed — an agent's `preview_transform` answer cut at its row limit did not say so. It now names the limit.
- Operations — the base compose file sets a 4 GB memory limit, and the service sizes its heavy work from it. At boot the service reads the container limit, keeps 1 GB for itself, and lets queries and chart pictures share the rest. The boot logs one
memory_budgetline, andmemory_budget_smallwhen the limit leaves room for less than one query. On a host with less than 4 GB, lowermem_limitin the instance override below the host's memory. On a larger host, raise it to run more at the same time. The install guide's Capacity section has the figures. - Operations — the examples page has its own file and its own public path.
ARKUSH_EXAMPLES_FILEnames the file,/data/pages/examples.mdin the image, and an admin edits it in the app. The page answers only whereARKUSH_ALLOW_PUBLISHINGis on. An identity proxy in front of the service must let/examplesthrough with no sign-in. - Operations — a Cloudflare zone in front of the service can hold the published pages. Set
ARKUSH_CLOUDFLARE_ZONE_IDandARKUSH_CLOUDFLARE_PURGE_TOKEN, with a token that has only the Cache Purge permission on that zone, and an httpsARKUSH_PUBLIC_URL. Every act that publishes, ends or replaces a published copy then removes its addresses from the zone at once, and Cloudflare holds a published page for five minutes. The zone needs a cache rule for/p/that takes the lifetime from the service's headers and leaves the query string out of the cache key. The boot refuses half the pair. Unset, nothing changes. - Licence — the warranted list of destinations gained `api.cloudflare.com`. The service reaches it only when the operator sets the Cloudflare pair above, to purge published pages. The annexes of the agreements are updated with the complete list.
v2026.9.1
- Fixed — the image of v2026.9.0 was never published. The image scan found a critical flaw in
proxy-addr, a library that the agent connection's dependencies carry, and stopped the publish. This release updates it to the fixed version, and carries everything v2026.9.0 lists below. An install moves from v2026.8.0 straight to v2026.9.1.
v2026.9.0
- Sign in with GitHub, with a code from your email, or with a passkey. Every Sign in button opens one page that lists the ways in the server offers, in the order the operator chose. An email code is eight digits, works once, for 10 minutes. After a sign-in, the app offers once to add a passkey, so the next sign-in takes the device's fingerprint, face or PIN. The account menu's Passkeys panel lists, adds and removes them. The app never stores or asks for a password.
- Sliders and field choices that charts read. A slider param holds a number between two bounds, and charts, measure formulas and block titles read it by name:
sum(revenue) * (1 + growth), or{growth}in a title. A field-choice param is a dropdown of columns and measures, and a chart channel or a scorecard set to Follows it changes its breakdown or its metric, with the axis title and the number format. The rail panel that lists them is now Controls. - Charts plot measures by name. A chart channel can name a measure such as ROAS, and the chart computes the ratio per category correctly. The field picker offers measures. A chart shape the computation does not handle shows a message that says why, never a wrong number.
- Filter from any chart. A click on any mark or legend entry filters the dashboard, Ctrl, Cmd or Shift adds a value, and a click on empty space clears. A drag across a date or number axis filters to a range, and a scatter plot takes a box. The clicked chart fades what is outside the filter. With the keyboard, Tab reaches a chart, the arrow keys walk its marks, Enter filters and Escape clears. The chart form lists every field a click or a drag can filter by.
- Version history reaches back 90 days. Each meaningful change is its own version, and repeated moves of one block merge into one. A refresh, a delivery or a comment no longer makes a version. Older versions thin by age: every save for 15 minutes, every change for 2 days, then the last state of each day. The list groups versions by day, and a version's link carries its number, such as
?version=42. - A broken block no longer breaks the dashboard. A save that would store a block no reader can draw is refused, with the block, the field and the fix named, from the canvas, the Source panel and agents alike. A block that fails to draw shows its own message, and the rest of the dashboard draws.
- Read a Cloud SQL PostgreSQL database. On an org install whose operator registers a database, a datasource can run SQL on it as a registered service account, in a read-only session, with no stored password.
- An empty dashboard shows only the steps you can take now: upload a file, connect BigQuery, use a shared datasource, and connect an agent until one is connected.
- The help is written for arkush.app. It opens on how a dashboard works, and adds two pages: what arkush is and is not, and the directions it may take. A section that only an org install offers says so under its heading.
- The app shows a page when something fails. A drawing error offers Reload page, an address with no page shows Page not found, and animation stops when the system asks for reduced motion.
- Smaller changes. The Connect an agent panel lists all eight agent clients. Resting the pointer on a column name in the chart spec editor shows the column's profile. Side panels carry headings that screen readers can jump between. The app has a new mark: an A4 sheet with an eight-point star.
- Fixed — a click on a line chart filtered to the wrong date. A click on a line, an area or a daily bar now filters to the date under the pointer, and a field a chart transform made writes no filter instead of a guessed one.
- Fixed — the first save of a new dashboard closed the open datasource editor.
- Fixed — a dashboard owner whose address has capitals was treated as someone else on the sharing panel, the lists, the Users page and when publishing.
- Fixed — a server-side fetch could reach an address other than the one it checked. A fetch for a web-address datasource or for an agent's sign-in details now connects only to the address it checked.
- Fixed — a failed service-account token named no cause. The message now names it, and Verify on the service account shows the command that fixes it.
- Agents — `get_dashboard` answers a short YAML outline of the params, the datasources one line per column, and the blocks with their index. The new
partsargument returns any part of the document in full, andparts: ["/"]returns the whole document as before.get_sandboxandget_shared_datasourceanswer the same compact column lines.list_databasesis new.create_dashboardtakes only format version 5, and every tool refuses an argument it does not take and names the ones it takes. Refusals name the failing operation, the blocks and the fix, and a dashboard an agent creates takes the house theme.validatereports a click filter on a field a chart transform makes, and checks every slider bound and dropdown choice. - Operations — the state database moves to schema version 4. The steps add a column to the sessions table and rebuild the version archive: consecutive versions with the same content, such as refresh-only or comment-only saves, are merged into one. Before the steps, the service writes
state.db.before-v2beside the database, and the boot logs oneschema_upgradeline. To roll back, stop the stack, replacestate.dbwithstate.db.before-v2, deletestate.db-walandstate.db-shm, and start the previous image. Work saved after the upgrade is not in the copy. The steps are indeploy/README.md→ Versions and upgrades. The document format stays at version 5. - Operations — the service backs itself up while it runs. Every
ARKUSH_BACKUP_HOURS(24 by default) it copies the database and every stored file tobackups/in the state directory and keeps the newest two, with no stop of the stack.SIGUSR2to the container takes a backup at once, and0oroffturns the timer off. The state disk needs free space for two more copies of the state directory. - Operations — new sign-in settings, all optional. With
ARKUSH_SIGNIN_PROVIDERSunset and the Google client pair set, a server offers Google alone, as before. To offer more, list them inARKUSH_SIGNIN_PROVIDERS(google,github,email,passkey, in page order). GitHub needsARKUSH_SIGNIN_GITHUB_CLIENT_IDandARKUSH_SIGNIN_GITHUB_CLIENT_SECRET, andARKUSH_SIGNIN_GITHUB_ORGSlimits it to members of named organizations. Email codes need a mail relay inARKUSH_SMTP_HOST,ARKUSH_SMTP_PORT,ARKUSH_SMTP_USER,ARKUSH_SMTP_PASSWORDandARKUSH_SMTP_FROM, and the server sends at mostARKUSH_SMTP_MAX_PER_HOURcodes an hour (60 by default). The boot refuses a listed method without its settings, and settings for a method that is not listed.ARKUSH_PROXY_HOPS(1 by default) is the number of proxies in front of the service, so the sign-in limits count the client's real address: set it when more than one proxy forwards requests. - Operations — Google sign-in changes for some addresses on an allowlist of domains or addresses. Where
ARKUSH_SIGNIN_ALLOWadmits people by a domain or an address entry, Google signs a person in directly only for a Gmail or Google Workspace address. A Google account on any other address gets an email code first, and is refused where email codes are off. WithARKUSH_SIGNIN_ALLOW=*, Google sign-in works as before. Under a domain entry inARKUSH_SIGNIN_ALLOW, such as@example.org, an address with a+tagis refused with the plain address named, and an open session of such an address ends at its next request. - Operations — registered databases and new log lines.
ARKUSH_DATABASES_FILEnames the hand-edited database registry, and with no file nothing changes. The install guide's new section Read a Cloud SQL database lists the setup. The service logssnapshot_prune_failedandsnapshot_drop_failedwhen a save or a delete leaves stored rows behind. Server-side requests now sendUser-Agent: arkush/<version>. - Licence — the warranted list of destinations gained two hosts and two operator-set destinations. Where the operator lists GitHub sign-in, the service connects to
github.comandapi.github.com. The mail relay inARKUSH_SMTP_HOSTand the databases in the database registry are destinations the operator sets.MIT-0joins the accepted licences of dependencies. The annex of every signed agreement is updated with the complete lists. - Privacy — update the stored-data list, the retention periods and the cookies. The service now stores email codes (the typed address and a hash of the code, for at most an hour), passkeys (the email and a public key), its own backups (the newest two, so a deleted record lives on in a backup until that backup is replaced), and the database registry (addresses and certificates, no password). The secrets gain the GitHub client secret and the mail relay password. Version history is now kept at most 90 days and 50 MB per dashboard, in place of the newest 50 versions. New cookies:
arkush_signin_code,arkush_signin_passkeyandarkush_passkey_add. The browser keeps two new keys:arkush.passkeyOfferDismissed, andarkush.chunkReloadAtin session storage. Update each instance's privacy page to matchdocs/security-overview.md→ What is stored where, and for how long.
v2026.8.0
- Help moves to docs.arkush.app, with pictures on every page. The guides, the admin guide and the reference pages now live on one public site beside the install guide. Every page carries pictures cropped to what it explains and short silent clips of each act, in a light and a dark copy that follows your system setting. The Help links in the app, the error notices and the agent's error replies open the site in a new tab, and carry nothing about your server: not its address and not its version.
- A theme holds a light face and a dark face. A dashboard in a paired theme follows each viewer's light or dark mode. New dashboards open in the Arkush look on a paper grain, with Lifted to choose from, and the plain pair is now named Classic. A theme with one face keeps its look.
- The paper look reaches every page. The landing page, the legal pages, the install guide and the app's own pages take it. The account menu's Plain background setting turns the grain off in this browser, and a system set to more contrast turns it off too.
- Blocks resize from every side and corner.
- Clicking a bar in a date chart filters to that period.
- The Help panel holds links. It lists the guides and Connect an agent, and its foot names your server, its legal pages and its security contact.
- The landing page leads with Sign in with Google, shows an agent building a dashboard, and keeps the account-free door as a link.
- A public deployment can offer a waitlist for the features an org install sets up: Slack subscriptions, channel deliveries and server-side BigQuery. See the Operations item.
- Fixed — connector lines drew no line, only their arrowheads. Two separator lines and one text color were missing for the same reason.
- Fixed — charts drew dates on the reader's clock. A chart over calendar dates now shows the day the data states in every time zone, on the axis, in the tooltip and in a click that filters by date.
- Fixed — a dark view look over a light app left table text and scorecard numbers dark, and links in Administration were unreadable in dark mode.
- Fixed — version history started at the save before the newest. The list and its caption now lead with the newest save and name who made it, an agent included.
- Fixed — an empty Deliveries file could not be saved, though the editor said an empty file delivers nowhere. Every refusal of that file now says what to write.
- Fixed — messages that named no fix. A paused scheduled refresh names each datasource's cause and the act that lifts it, and a datasource with no name is refused at its Name field.
- Fixed — the paper grain stayed one size while the canvas zoomed.
- Operations — `/help` on an install now redirects to docs.arkush.app. Each
/help/…address answers a temporary redirect to the same page on the site, so bookmarks and older links still land. The image carries no help pages, and an install without internet access has no help. The path stays on the list of public paths an identity proxy lets through until the first release that is in a later year and at least ninety days after this one, and then leaves it. - Operations — one new optional variable, `ARKUSH_WAITLIST`. Unset, nothing changes.
trueturns on the waitlist, which stores a signed-in person's email address, the feature and the date instate.db. The schema version and the document format do not change. - Privacy — add waitlist entries to the stored-data list, where the waitlist is on. Each entry is one person's email address, the feature they joined the waitlist for, and the date. It stays until the person leaves the waitlist or deletes their profile. An instance that leaves
ARKUSH_WAITLISTunset stores none and changes nothing. Otherwise, update its privacy page to matchdocs/security-overview.md→ What is stored where, and for how long.
v2026.7.0
- Split a dashboard into pages with frames. A frame is a named sheet on the canvas: add one from the header's Add menu, name it, and drag blocks onto it. A reader selects Pages to read one frame at a time, moves between pages with the tabs or the arrow keys, and selects Present to show them fullscreen. The address holds the open page, so a copied link opens the same page. On a phone a dashboard with frames opens on its pages. A control can apply to every block on a frame, a published dashboard keeps its pages, and a scheduled send lists its blocks by page.
- The header's block palette names its blocks. Chart adds a chart in one click. Add opens a menu of every block type in three groups, Data, Content and Layout, and each type has one line that says what the block is.
- Bullet and combo charts have a form. In Properties, a bullet chart takes its Category, Actual and Target fields, and a combo chart its X axis, Bars and Line fields. A number field adds up per category, and its aggregate can be changed below the field.
- A shared datasource keeps one stored copy of its rows. A scheduled refresh replaces that copy and no longer archives another full copy with each version. The details page and Use in new dashboard work as before.
- Fixed — a dashboard refreshed from the app stored its rows in its version history. The rows now go to the snapshot store, as they do with a scheduled refresh.
- Fixed — copying a shared datasource with no stored data. The app and the agent tool
add_shared_datasourceboth refuse the copy, and tell the reader to ask the owner to refresh the datasource in the datasource library. Before, the agent tool made a copy with no data. - Operations — the first start takes old snapshot rows out of the database. A dashboard or a shared datasource that an earlier release stored with its rows inside moves them to the snapshot store, and every archived version drops its rows. The start takes longer once, and the boot logs one
inline_rows_shedline with the counts and the bytes freed. The database file keeps its size until you compact it. To get the space back, follow the install guide → Compact the database. The step needs free disk larger thanstate.db, and the service is down while it runs. The schema version and the document format do not change. - Privacy — add shared datasources to the stored-data list. A shared datasource is an entry with up to 50 archived versions and no snapshot rows, and its rows are one payload in the snapshot store. Archived versions of dashboards and shared datasources hold no snapshot rows. Update each instance's privacy page to match
docs/security-overview.md→ What is stored where, and for how long.
v2026.6.0
- An inbox tells you what happened on your dashboards while you were away. An entry appears when someone shares a dashboard with you or hands one over to you, starts a comment thread on a dashboard you own or replies in a thread you are in, and when a scheduled refresh or a subscription stops. The Inbox button in the header shows how many entries are new. An entry goes away by itself when its problem clears, and Done puts one away. The share dialog has a Notify checkbox, on for a person and off for a group.
- Articles put a written finding beside the canvas. An article is markdown text with charts placed in it, and each chart keeps the filters its author set when it was placed. When the data under an article changes, the article says the text may no longer match. Open Articles on the right icon rail. A reader uses Open in dashboard under a chart to explore from that point, and a published dashboard publishes its articles with it.
- Changes by other people show on an open dashboard. When another editor, an agent or the scheduled refresh changes the dashboard you have open, the change arrives in place and a notice names who changed what. Your own unsaved edits stay. While an agent works, the header shows Agent editing. A change from your own agent or your own other tab has Undo.
- A collision keeps your version. When you and someone else change the same part of a dashboard, the dashboard shows their version of that part and keeps a copy of yours in version history as Set aside. The notice offers Compare and Put mine back.
- Leave a dashboard shared with you. Leave is in the Share panel and in the Dashboards page's row menu. The owner gets no message. Transfer ownership now follows the instance's sharing setting, so on an instance where only admins share, only an admin hands a dashboard to a new owner.
- A CSV with uneven rows imports, and says what it changed. A row with fewer fields than the header reads with its missing cells empty, and a row with more fields loses the fields past the header. Before you create the datasource, a note counts those rows and names the file line of the first one. Before, such a file read as one column with no message.
- A time with a time zone imports as UTC. A timestamp such as
2024-06-15T12:30:00+02:00is stored as2024-06-15T10:30:00Z, in the browser and on the server alike, whatever the zone of the machine. - A stopped scheduled refresh shows in the Data panel. When the schedule refused a datasource or its run failed, the Scheduled refresh section names it. An editor reads why and runs it once to resume the schedule, and a viewer is told to ask an editor.
- A folded chart shows your field labels. On a chart that folds several columns into one, the axis, legend or header prints each column's label, and the value axis takes the format the folded columns share.
- Clearer chart forms. The field type reads Number, Category, Ordered and Time, as in the Data panel. Label rotation shows its angle. A format with nothing set reads "Automatic", and an override reads "set on this block". A new chart at its default size shows every chart type without a scroll.
- A control takes its field's name. A new control's title becomes the name of the field it filters. A title you typed stays.
- A file dropped on a read-only dashboard says how to add it. The notice names the way out: switch to Edit, go back to the current version, leave the sandbox, use a larger screen, or ask the owner for edit access.
- Fixed — a scorecard or a pivot value moved to a measure kept a hidden reducer. The reducer is now removed in the same edit.
- Fixed — a marquee drawn inside a large section selected the section. A section now joins the selection only when the marquee encloses it whole.
- Fixed — smaller issues. An empty category reads
(Blank)also on a chart channel with no written type. A filter chip on a block with no data says to run the datasource. Changing a control's type clears its current value and says so. View mode starts with no block selected. A value missing from the snapshot can be removed from a field's order. A refused address stays in the sharing field for you to correct. A sheet on an instance that reads no private sheet says to use Import. A reload of a dashboard with no unsaved edits shows the owner's newer save instead of an overwrite banner. A Google account with no Google Cloud project reads how to create one, and a refused query names the project it billed. - Operations — on a header-trust install that allows anonymous use (`ARKUSH_ALLOW_ANONYMOUS`), the front now also skips the app's own pages:
/dashboards,/d/<id>,/datasources,/files/<id>,/users,/adminand each administration area, beside/appand/api/me. A reload or a bookmark on those pages then opens the app. Print the list again withnode scripts/proxy-paths.mjs --anonymousand update the proxy configuration. An org install changes nothing. - Agents — `render_block` reports what Vega-Lite warned while it drew the chart, as
findingsin the shapevalidateuses. A call whose arguments the tool's schema refuses, or a call to an unknown tool, now answers with a refusal that carriesreason,fixerandhelp, with the codebad-request.validatenames an unknown mark inside a layer or a composition, and advises on a category label that the axis cuts short.render_blocktakesview, the id of a view in an article, to draw the chart with the filters that view keeps.patch_dashboardwrites articles undermeta.articles. On a server with no delivery destinations, the agent is told that scheduled sends are not set up. - Operations — the state database moves to schema version 2. The step adds one column,
kind, to the version archive. Before the step, the service writes a copy of the database beside it,state.db.before-v1, and the boot logs oneschema_upgradeline that names the copy. To roll back, stop the stack, replacestate.dbwithstate.db.before-v1, deletestate.db-walandstate.db-shm, and start the previous image. A version set aside after the upgrade is not in the copy. The steps are indeploy/README.md→ Versions and upgrades. The document format stays at version 5. - Operations — new log events.
version_set_aside,inbox_mute,inbox_unmuteandprofile_unreadableare new, andrefresh_tickcarriessetAsidesDropped. Revoke all and Delete profile also remove the person's inbox. The install guide's event table lists every field. - Privacy — the version history keeps set-aside versions. When two people edit the same part of a dashboard at the same time, the working copy of the editor whose edit lost is kept in the history as a set-aside version, with no snapshot rows, and never becomes the dashboard. A dashboard keeps up to 10 set-aside versions beside its 50 archived versions, and each one is deleted after 30 days. Update each instance's privacy page to match
docs/security-overview.md→ What is stored where, and for how long. - Privacy — add the inbox to the stored-data list. The service stores each person's inbox entries and the dashboards they muted. An entry stays for 90 days. An entry about a failing scheduled refresh keeps why it stopped: the reason can name the address of the person whose run the schedule acts for, and it can quote a warehouse error, and it shows only to people who may edit the dashboard. Revoke all and Delete profile remove a person's inbox. Update each instance's privacy page to match
docs/security-overview.md→ What is stored where, and for how long.
v2026.5.0
- Every dataset opens on its columns. The Data workspace shows one tile per column: its type, how many values are missing, its distribution or top values, and its key numbers. A click on a value filters and sorts the rows, and one step charts any column. Filters and sorts there never change the dashboard.
- An import shows its data before anything is saved. Every way in — a dropped file, the Data panel, Replace data — stages the file in the Data workspace with its column profiles and the row grid, and Replace data lists the columns added, dropped or retyped.
- Hover a field to see its data. A field on a chart, a table name in the SQL editor or a datasource in Add data shows its column line on hover or focus, with a way into the workspace.
- The Data workspace has its own address, and it opens read-only for viewers and on phones. Shared datasources and uploaded files open there too, and a shared datasource's SQL is changed where it lives.
- The chart spec editor opens in one step from Properties. Inside a formula it completes column and function names and marks errors. It keeps the compact Vega layout, also on paste, and shows Vega-Lite's own warnings.
- Every error says who can fix it. An error message now links Who can fix this to its entry on the new help page, Error messages, which says what happened, who can fix it — you, the owner, an admin, or the operator — and what to do.
- An empty category reads `(Blank)` everywhere you read it: a chart's axis, legend and facet header, a table cell, a pivot key and the Color popover. A missing number still reads
–. - A chart that cannot draw says why. An editor reads the fix, and a viewer reads that an editor of the dashboard can fix it. A field the datasource lacks reads
(missing)on the encoding shelf. - Smaller editing changes. Refresh on a datasource row runs in place and reports by a notice. A note has a small form for its title and its text. A color swatch takes a hex value and offers the theme's colors, and a color channel with no colors set offers Set colors per value. Edit as JSON moves to the foot of the Properties form. The encoding shelf moves the canvas so that it never covers the chart it edits.
- Fixed — a CSV saved on Windows imports with its accented characters intact, dates that a transform makes read as dates, and the SQL editor in the Data workspace shows a whole query.
- Fixed — the Findings panel showed the previous dashboard's numbers after a switch, and a double-click on a chart title near the canvas's left edge opened the shelf instead of renaming the chart.
- Operations — the logs name people by pseudonym. No log line holds an email address: each person is
u_and 16 hex characters, the same on every line of one instance. A usage dashboard or a log view that readsuseras an email shows pseudonyms after the update. An admin finds a person's pseudonym under Administration → Users, anddocker exec <container> node /app/scripts/pseudonym.mjs <email>prints it on the host. - Operations — a new key file, `log-secret`, beside `state.db`. The first boot mints it. Keep it in the backup: without it, the lines after a restore name each person by a new pseudonym.
- Operations — `ARKUSH_LOG_LEVEL` is new and optional.
info, the default, writes every line.errorwrites the error lines and the failure events. The base compose file passes it through, so copy the compose file out of the new image as the upgrade steps say. - Operations — the logs are easier to read. A failure that the scheduled sweep repeats writes its first line, then one line an hour with a count, and one line when it clears. Each agent tool call writes one line with the tool and its outcome, never its arguments. The install guide's new Monitoring sections cover reading the logs, sharing one request id with a proxy, an optional route that sends the lines to Google Cloud, and every event with its fields. No schema step and no document format change: the state database stays at schema version 1, and the document format stays at version 5.
- Agents — a refusal's `reason` is an error code, and a refusal also carries `fixer` and `help`.
fixernames who can fix the situation, andhelpis the path of its entry on the Error messages page. The old reasons map to codes:forbidden→not-shared,no-edit-access,not-yours,not-a-save, or the run check'sservice-account-denied,no-service-account,not-signed-in,service-account-setup;no-bigquery,no-sandboxes,no-sa-registry,no-file-store,no-fetch-hosts,no-credential→not-configured;no-such-part,no-such-block,no-such-param,missing-datasource→not-found;missing-inputs→no-snapshot;local-sql,bq-sql,bq-not-found→sql-error;invalid-sandbox→invalid-doc;bad-op,bad-id,bad-url,bad-param-value→bad-value;bq-auth,impersonation-failed→service-account-setup;bq-permission→warehouse-permission;bq-bytes-billed→bytes-billed;bq-network,bq-http,http,network→upstream-unreachable;bq-timeout,render-budget→run-timeout;no-file-binding,no-url-binding→wrong-source;missing-file,bad-file-entry→file-missing;file-not-shared→not-shared;empty-source→file-not-readable;host-not-allowed→address-not-allowed;private-address,insecure-redirect→address-refused;unauthorized→source-not-readable;empty-spec,not-renderable,no-flows,external-data→render-failed. A catalog read of a table that does not exist answersnot-found. - Privacy — add the log key to the stored-data list. The service stores one more item: the log pseudonym key, a file beside the database. Log lines now hold pseudonyms and no email addresses. Update each instance's privacy page to match
docs/security-overview.md→ What is stored where, and for how long.
v2026.4.0
- A viewer builds their own charts in a sandbox. On a dashboard you can view but not edit, the header's switch reads View | Sandbox. In Sandbox, the dashboard's blocks lock, and you add charts, tables, notes and derived views over the data the dashboard already holds. Copy to sandbox on a chart's menu gives you a copy to change. The sandbox runs no warehouse query and never changes the dashboard. You and the dashboard's editors see it, and nobody else.
- Editors adopt a chart from a sandbox. The Sandboxes panel lists every sandbox on the dashboard. An editor opens one and uses Add to dashboard on a chart, or Replace on a chart that began as a copy of a dashboard chart. The chart brings the derived views it reads, and its owner sees a notice at their next open. Dashboard settings carries "Let viewers build their own charts in a sandbox", on by default.
- Every filter is in one Filters panel. The panel beside the canvas lists every param of the dashboard with its control, also a param that only chart clicks set. Viewers open it too.
- A dashboard with many charts opens faster. A resize now re-fits only the chart that changed, and a dashboard draws each chart once when it opens, where it drew each chart twice before.
- Revoke all also removes the person's subscriptions. In Administration → Users, Revoke all now ends the agent grants, the browser sessions and the subscriptions of the account in one act. Before, a person who left kept receiving their subscribed dashboards on an instance whose sign-in list names no one person. The confirmation says so.
- A session ends 90 days after its sign-in. A session still renews while in use, up to 90 days after the person signed in. Then the person signs in again.
- Seven smaller findings from a security review are closed. Among them, the agent consent page names the host that the approval returns to.
docs/security-overview.mdholds the current facts. - Fixed — a finding's jump to its block left an off-screen block out of view. The jump now pans the canvas to the block.
- Operations — a person who signed in more than 90 days ago signs in once more after the update. Nothing to set.
- Operations — offboarding has one more step. When a person leaves, use Remove all sandboxes under Users after Revoke all. A person's sandboxes stay until then, so that editors can still adopt a chart from them. The install guide's offboarding steps include it.
- Operations — the image runs Node 24.21. No schema step and no document format change: the state database stays at schema version 1, and the document format stays at version 5.
- Agents — four sandbox tools.
get_sandboxandpatch_sandboxread and change the caller's own sandbox over a dashboard.list_sandboxeslists the sandboxes on a dashboard for its editors, andadopt_blockmoves a sandbox chart into the dashboard.run_datasource,preview_transformandrender_blocktakesandbox: trueto work over the caller's sandbox. Apatch_dashboardthat renames or removes a datasource a sandbox chart reads answerssandboxWarningandsandboxesBroken. - Privacy — add sandboxes to the stored-data list. The service stores one more kind of data: a sandbox, one person's own charts and derived views over one dashboard, with the rows of those views. A sandbox stays after its person leaves until an admin removes it. Browser sessions now end at most 90 days after the sign-in. Update each instance's privacy page and terms to match
docs/security-overview.md→ What is stored where, and for how long.
v2026.3.0
- Operations — read this before you update: a person uses a service account through Service Account User, and Token Creator no longer admits a person. Neither image reads the other role, so grant the new role before you update and remove the old one after. Before you update: on each registered account (Administration → Credentials lists them), list the policy with
gcloud iam service-accounts get-iam-policy <account> --format=json, and give everyuser:,group:ordomain:member that holdsroles/iam.serviceAccountTokenCreatorthe roleroles/iam.serviceAccountUseron the account itself, for examplegcloud iam service-accounts add-iam-policy-binding <account> --member="user:<email>" --role="roles/iam.serviceAccountUser". Skip the runtime identity of the service: it keeps Token Creator and its policy-read role. After the new image starts and a person sees the account under Runs as, remove Token Creator from each member you moved, withremove-iam-policy-bindingand the same member. A grant on the project of the account does not count. The install guide's section "Upgrade from a release before 2026.3.0" holds these steps in order, and a person you do not move gets a refusal that names the missing role. - Operations — optional: grant a team once through a Google group. A Google group that holds Service Account User on a registered account now admits its members on every deployment, whichever groups backend it runs, and the group needs no registration in the app. To use group grants, enable the Cloud Identity API (
cloudidentity.googleapis.com) in the project that holds the runtime identity of the service, and have a Google Workspace super admin give that identity the Groups Reader admin role in the Admin Console. Do this before the first group grant: without it, a group grants nobody, and the refusal names the step. To move a team through a group at this update, do this first, then grant the group in place of each person. The install guide's section "Named service accounts" has the commands. A deployment whose policies name no group sends no new request. - A Google group can give a team its access to a service account. An admin grants the role to the group once, and a new colleague gets access by joining the group in Google.
- A datasource that runs as an account you cannot use now shows the way back. Its Runs as field offers Your own Google account, and the note beside it says why the account does not run for you. Refresh and Refresh all name the same switch instead of sending you to an admin.
- Your access to a service account no longer lets you take its token home. A person who runs queries under a registered service account now needs the Service Account User role on that account. That role mints no token, so the person cannot read the data of the account outside the app. Only the service mints tokens. The install guide says where to keep the accounts, so that the role allows nothing else of use.
- A comment always carries the name of the person who wrote it. Every save writes a new comment under the account that saves it, from the app and from an agent alike. A comment already on the dashboard keeps its author. An agent that acts for you reads a comment as yours only when you wrote it.
- Import makes a copy that you own. An imported dashboard starts private, as a duplicate does. It drops the shares, the visibility, the maturity stamp, the scheduled sends and the comment threads of the file. It keeps the blocks, the datasources, the data, the tags, the contact and the refresh schedule. Import is not a backup.
- An open dashboard answers its repeated requests faster. The checks an open tab repeats — the version check, the data downloads, who else is here, the live updates — decide access from a stored summary of the dashboard and no longer read the whole dashboard each time. On a large dashboard a check falls from tens of milliseconds to a fraction of one. The first start after the update reads every dashboard once to build the summaries, which takes a few seconds on a large instance.
- The sharing settings of an uploaded file are checked when you save them. A save with an unknown visibility value or a malformed share is refused, and the message names the value. A file saved before this check still opens.
- The health endpoint names the build.
/healthzalso answersrevision, the full commit that the image was built from. On a deployment that tracks the main branch, compare it with the latest commit to see which build runs. A service that runs from source answers norevision. - The boot log says when an instance record cannot be read. If the stored name, logo and security contact cannot be read, the boot logs
instance_identity_unreadable, and/.well-known/security.txtanswers 404 until an admin saves the identity again. If the stored access settings cannot be read, the boot logsinstance_policy_unreadable, and the instance runs on the default access settings until an admin saves them again. - Agents — a comment you add with `patch_dashboard` or `create_dashboard` is written under the account your grant acts for, whatever
authorit carries. A warehouse refusal names the Service Account User role, and a refusal read by an admin prints the command that grants it on the account.
v2026.2.0
- Publish a dashboard to the internet. The owner of a dashboard publishes it as a frozen copy at its own address, and anyone reads that copy with no account. The copy never queries the warehouse and holds no email address. It names the owner and each person who refreshed its data by a username. Each chart also gets its own page and a picture for link previews. Publishing again after a change updates the copy at the same address. Publishing is off on every instance until the operator turns it on.
- A Google Sheet shared by link reads with no sign-in. A sheet shared as "anyone with the link" reads in the browser with no credential, also for a visitor with no account. A private sheet reads with the person's own Google account, as before.
- Administration has seven areas, each at its own address. Users, Access, Credentials, Deliveries, Appearance, Pages and Configuration are separate pages under
/admin. Each deployment setting shows its effective value in the area that uses it. - Admins decide three more things about access. In Access, an admin sets who may connect an agent, who may take rows away as a file, and who may open a dashboard to everyone signed in. "Take rows away" covers Data as CSV, the export of a dashboard, the tables in a subscription, and the rows an agent reads. Each setting starts at its widest value, so an instance behaves as before until an admin changes it.
- One list shows what leaves the instance on a schedule. The Deliveries area lists every scheduled send and where it goes.
- Pick the service account in the datasource editor. A BigQuery datasource names its registered service account in the Runs-as field, and the table browser lists what that account can read. This applies where the operator registers service accounts.
- The table form starts from rows and values. Rows and Values come first in the form, and a field in Columns makes the table a crosstab. The separate pivot switch is gone.
- An instance open to any Google account offers only what it can do. On such an instance, only an admin shares a dashboard, and only its owner and an admin open the live dashboard. Everyone else reads the published copy, where the owner published one. The app hides each act the instance cannot perform, or says why it is not available.
- Help, About and the changelog are pages. The Help panel keeps the guides. About, the changelog and the list of what needs a source open as their own pages. After an update, a notice shows once with a link to what changed.
- Messages say what to do next. A refusal or an error in the app and from the server states what failed and an act the reader can take. It no longer names a setting or a person that the reader cannot reach.
- Scheduled sends carry only authorized rows. A scheduled delivery posts only rows that an authorized run wrote. Send now asks the rows setting of the subscriber, the same as the scheduled send. Before each run, a scheduled refresh or delivery checks that the person it runs for can still sign in.
- Keys moved out of the database. The session signing key and the snapshot attestation key live in the environment or in files beside the database. A copy of the database alone cannot sign a person in or vouch for a snapshot.
- Limits on large jobs. The server draws chart images in a separate worker with a memory limit and a time limit, and the server-side DuckDB engine has a memory limit. A job over its limit fails with a message and leaves the service running.
- Stricter checks on input. The server checks the sign-in return address, the upload quota, the registry files and the form of each request more strictly.
- The code editor moves to monaco-editor 0.56, with its bundled DOMPurify at 3.4.8.
- Fixed — Create accepted a datasource name the dashboard already used. Create now refuses that name. A table listing that timed out named a query that never ran, and now names its own time limit.
- Operations — every person signs in again once. At the first boot, the service deletes the old session signing key from the state database and makes a new one. With no
ARKUSH_SESSION_SECRETset, the key goes into a 0600 filesession-secretbesidestate.db. Set the variable to keep the key out of the state volume and out of every backup. Agent grants stay valid. - Operations — every scheduled refresh and delivery pauses once. The attestation key moves the same way, to
ARKUSH_ATTEST_SECRETor anattest-secretfile, and the attestation now also covers the rows. No attestation written before this release verifies. Run each server-run datasource once, from the app or withrun_datasource, and its schedule resumes. A delivery showsrows-unsealeduntil then. - Operations — publishing has its own switch and its own public path. Set
ARKUSH_ALLOW_PUBLISHING=truein the override'senvironmentblock to turn publishing on. Unset, no dashboard is published and every published address answers 404. Published pages are served under/p/. An identity proxy in front of the service must let/p/through with no sign-in. - Operations — the base compose file changed, and each release is signed in every registry. The compose file adds
ARKUSH_SESSION_SECRET,ARKUSH_ATTEST_SECRETandARKUSH_FETCH_HOSTS. The upgrade steps in the install guide, under Versions and upgrades, verify the image signature and copy the file out of the new image. The install guide is published at docs.arkush.app. - Operations — no schema step and no document format change. The state database schema stays at version 1, and the document format stays at version 5.
- Agents — `list_bigquery_datasets` takes `service_account`. The tool lists datasets as the named registered account, the same as
list_bigquery_tablesandget_bigquery_table. - Agents — an agent acts only on its own person's comments.
get_dashboardmarks each comment and reply that the grant's own person wrote withyours: true. The agent treats every other comment as a request to discuss with its author, and changes nothing for it. - Agents — `list_dashboards` shows who a dashboard is shared with only where the caller may edit it. Every row still carries the owner and the number of shares.
- Privacy — update the stored-data list and the note on BigQuery jobs. The service stores two new kinds of data: published copies, one per published dashboard, and usernames. The session signing key and the attestation key move from the state database to files beside it, or to the environment. Every BigQuery job the server runs carries labels that name the instance, the dashboard, the datasource, the person's address and how the run started. Google keeps those labels in its job history under its own retention. The browser stores one more key,
arkush.seenVersion. Update each instance's privacy page and terms to matchdocs/security-overview.md→ What is stored where, and for how long. - Licence — the warranted list of destinations gained two browser hosts. A browser that reads a Google Sheet by its link connects to
docs.google.comand*.googleusercontent.com. The annex of every signed agreement is updated with the complete list.
v2026.1.1
- Fixed — a dashboard whose snapshot the server stores as a Parquet file showed empty charts in the browser. Since v2026.1.0 a snapshot above 256 KB of rows is stored as a Parquet file. The browser's DuckDB engine reads that file through its Parquet extension, which it fetches from the app's own origin. The image carried only the JSON extension, so the fetch answered 404. The browser reported that the snapshot data could not be loaded, and every derived view over that datasource failed with a missing-table error. The image now carries both extensions. Nothing to run: reload the dashboard.
v2026.1.0
- Arkush leaves alpha. The landing page, the band under the header and the Help panel no longer warn that the document format changes without migration, because it no longer does: a dashboard saved under an earlier version opens in this one, the format migrates as the dashboard is read, and a change to the state database carries its own numbered step with a copy taken before it. The band now says where saved dashboards live and who can read them, once per machine. An exported copy (Dashboards → Export open dashboard) is still the one copy nobody else holds.
- Operations — versions are now year.release.fix, and this is the first tag under the scheme. The middle number counts feature releases in the year and restarts every January; the last number counts fix-only tags on a release. A fix tag carries fixes only and is safe to take blind: the release check refuses a fix tag on which any setting, format, schema, tool or public path moved. The number says how old an install is and nothing about compatibility — the Operations item on each entry carries that. A renamed or removed setting or tool keeps working until the first release that is both in a later year and at least ninety days after the announcement, and says so at boot or in the tool's description until then. No fix is backported: an install several versions behind moves forward through boot refusals that each name the next step. The
0.xtags stay as they are. - Operations — the state database carries a schema version, and this release stamps it. At boot the service reads SQLite's
user_versionand compares it with its own, which is 1. A database at zero — every install before this release — is read as version 1 and stamped, with no copy and no step. From here on, a release that changes a table names the new version in its Operations item, and before the first pending step the service writes a copy of the database beside it, named for the version it came from. Rollback across a step is that copy; within one version it is the previous image on the same state directory. A database a newer image wrote refuses this image's boot and names the lowest image that can read it. The upgrade and rollback steps are indeploy/README.md→ Versions and upgrades. - Fixed — two writes racing one stored payload name can no longer overwrite each other. The store publishes a snapshot payload or an uploaded file exclusively, so the first writer holds the name and a second writer carrying different bytes is refused, the way a later writer already was. A retry with the same bytes still succeeds.
- A warehouse datasource that names no account says so, everywhere it matters. Since every server-side BigQuery run names a registered service account, a datasource naming none stops running on the server. It now reports itself in the dashboard's findings panel with the fix, its schedule pauses with the reason in the tick's report instead of failing, and the admin page lists every such datasource on the instance under Datasources without an account until the list is empty. The Runs-as picker in the datasource editor also explains an empty list: when the server cannot read an account's policy, or the policy does not name you, the picker shows the sentence and the command that fixes it.
- A dashboard's data moves out of its file. A snapshot's rows used to live inside the dashboard document, which made every save write every row again and made a data-heavy dashboard slow to edit. The rows now live in the snapshot store — one payload per dashboard and datasource, beside the dashboard rather than inside it — and the document carries the payload's name. Nothing changes on screen: the app reads the rows when it opens a dashboard and puts them back when it saves, and a save that only moved a block uploads no data at all. The per-dashboard byte budget is gone with the rows it counted; the 50,000-row cap per snapshot is unchanged. A dashboard you already have moves the first time you save it, with no step from you.
- Operations — the two one-time migration tools are gone, and the image no longer carries them. The pre-state-plane importer and the data-bar sweep, the eight retired storage variables that refused a boot by name, and the two runbook sections that drove them are all removed. Nothing to do: a deployment that already runs this software runs it the same way. A move from the layout the state plane replaced is now a restore from backup.
- Operations — read this before you update: a misspelled setting now stops the boot. The environment has no spell-check, so
ARKUSH_ADMINin place ofARKUSH_ADMINSused to start normally and grant nobody the admin right. AnyARKUSH_name the service does not read refuses the boot and names the line. Check your env file againstdeploy/README.mdbefore the update rather than after. - Operations — a true or false setting takes the word, and nothing else.
ARKUSH_ALLOW_ANONYMOUS=1used to read as false, so a deployment did the opposite of what its env file said. Writetrueorfalse; anything else refuses the boot. The other three value grammars — lists, byte sizes and durations — are written down in the deploy guide for the first time and are unchanged. - Operations — every `ARKUSH_` setting can be mounted as a file. Point
<NAME>_FILEat a file and the service reads the value out of it, trimming the trailing newline, which is the shape Docker and Kubernetes secrets produce. Setting both keeps the direct value. The settings that already end in_FILEare unaffected. - Operations — the browser's billing project no longer falls back to the server's.
ARKUSH_BROWSER_BQ_PROJECTused to default toARKUSH_BQ_PROJECT. The common configuration therefore served the instance's own project as every signed-in author's default. An author with job-create rights there billed the organization, and one without them read the refusal as the app being broken. SetARKUSH_BROWSER_BQ_PROJECTif your authors should keep a default; unset, each author picks their own project in the app. - Privacy — the kinds of data the software stores changed, and every instance's privacy policy and terms state them. Two rows move and one is new. Dashboard documents no longer hold snapshot rows, and snapshot payloads are now one record per dashboard and datasource — held in the state database below 256 KB of rows and as a Parquet file above it; the same data sits in a different place. The new row is the upgrade copy of the state database: before a schema step, the service writes a copy of the whole database beside it, holding everything the database held, and keeps one such copy at most. Nothing leaves the host. Update the stored-data list on each instance's privacy page to match
docs/security-overview.md→ What is stored where, and for how long. - Operations — the state database stops growing at the old rate, and the file gives its space back only on a rebuild. Each dashboard's rows leave its document on that dashboard's next save, and the version archive stops carrying a copy of them per version. Nothing to run: the change happens as people use the app. On an install whose database is mostly snapshot rows, the freed pages are reused for new data and the file itself stays at the size it reached. To hand that space back to the disk, stop the service and run
VACUUM INTOagainst a new file, then put the new file in place — the same stop the backup already needs. - Agents — nothing changes at your end, and one error is gone.
get_dashboardstill answers a few sample rows per datasource: a snapshot now keeps its own sample beside the column profile, so the tool shows what a row looks like without the rows being in the document. Long strings in the sample are truncated the way profile values already were. Thesnapshot-budgeterror no longer exists —run_datasourceandadd_shared_datasourcecannot answer with it, because the byte budget it named is gone. - Agents and the Source panel — a snapshot's `parquet` field is now `payload`. It names the stored data and the storage picks that data's home by size, so the old name sent a reader looking for a Parquet file that most snapshots never become. The value is unchanged — the same name for the same rows. A dashboard saved before this release is migrated as it is read; nothing to run.
- Composite charts are charts — a spec that facets, repeats or concatenates now lives in an ordinary chart block. The separate
chart_compositeblock type is gone. Nothing changes on screen: a composition still keeps the per-view sizes declared inside it, and still opens the raw-spec editor rather than the encoding form, because every surface now reads the composition off the spec instead of off a stored word. The stored word could disagree with the spec, and an agent writing a faceted spec into a plain chart block produced exactly that: a chart that rendered wrong and reported an advisory instead. That state is now unrepresentable. - A datasource says where its rows come from and whose credential runs them, in two fields instead of one word.
executionanswered three questions at once — the place, the engine, and the identity — souser_oauthandservice_accountwere two words for one BigQuery query under two credentials, and a sheet read by a person's own Google spelled that a third way. Nowsourcesays where the rows come from and carries that source's own address or file, andrunsAssays whose credential the run spends: nothing for the deployment's own, a service-account email for a registered one,userfor the signed-in person's own Google. Nothing changes on screen or on a schedule. The four fields that only meant something next to one word —serviceAccount,file,url,credential— are gone, and the combinations that used to need a warning cannot be written down any more. - Operations — the document format moves to version 5, and no step is needed from you. Dashboards migrate as they are read, in memory, and the next save writes the new shape. There is no bulk rewrite and no pass at boot. A dashboard saved under this release does not open in an older one, which is the usual direction: roll back by restoring the state-directory copy the schema runner writes.
- Operations — every scheduled datasource pauses once, by design. The server's proof that a snapshot came out of an authorized run is bound to the datasource's shape and to who ran it, and both moved in this release, so the proofs written before it no longer verify. Run each credentialed datasource once — from the app, or with
run_datasource— and its schedule resumes. This is the same pause a rotation of the attestation secret causes. - Agents — the block-type vocabulary lost a member.
chart_compositeis no longer a block type, andvalidatenames it as a finding wherever it is written. Write a composition astype: "chart"with facet, repeat or concat in the spec. A document you send at version 1 is migrated for you. The authoring guide and thecreate_dashboard,render_blockandvalidatedescriptions say so. - Agents — `execution` is gone; write `source` and `runsAs`.
{ "execution": "service_account" }becomes{ "source": { "kind": "bigquery" } },"duckdb"becomes{ "kind": "derived" },"local"becomes{ "kind": "imported" }, andfileandurlkeep their names and take their binding inside the source —{ "kind": "file", "entry": "…" },{ "kind": "url", "href": "https://…" }. A named service account moves fromserviceAccounttorunsAs, andcredential: "user"becomesrunsAs: "user". A document you send at an older version is migrated for you. The initialize instructions, the authoring guide and everyvalidatemessage say so. - Google decides who may use a service account. Running a warehouse query under a registered service account used to need the analysts allowlist, or a place on a list kept in the app's own registry file. Both are gone. The app reads the account's own IAM policy in Google Cloud and asks whether that person holds Service Account Token Creator on it — the same rule the account already follows everywhere else in Google. Grant the role to a person and they may use the account here, with no allowlist and no restart. Derived views and file-bound datasources still need no account and no right beyond editing.
- Operations — read this before you update: every registered service account needs a second grant. The service now reads each account's IAM policy, and the impersonation grant it already holds does not carry that permission. Add
roles/iam.serviceAccountViewerfor the service's own runtime identity on every registered account. Until you do, a run naming that account refuses with a message that prints the exact command. - Operations — who may run is no longer configured in this application. The
mayRunlist on a registry entry is retired. An entry that still carries one is read as text and grants nobody; the file needs no edit. Grantroles/iam.serviceAccountTokenCreatorin Google Cloud to each person who may use the account. - Operations — an IAM outage longer than an hour pauses server-side runs. The service keeps each policy for one hour after the last successful read. Past that, a run naming a service account refuses and its schedule pauses until Google answers again. Derived views, file-bound datasources and addressed reads are unaffected — none of them asks Google who may spend an account.
- Agents — a service account is Google's to authorize.
run_datasourceon a datasource naming a service account, and the catalog tools browsing as one, now need the Service Account Token Creator role held by the person who approved your grant. The analysts allowlist does not reach a named account. The refusal names the missing role and the command that grants it. The authoring guide says so. - Licence — the warranted document format version moved from 1 to 5. Every signed agreement warrants this number in its annex.
- Licence — the warranted list of destinations gained one host. The service reads a registered service account's IAM policy at
iam.googleapis.com, to ask Google who may use that account. It sends the account's own address and nothing else, it happens only where the service-account registry is set, and an answer is kept at most an hour. Every signed agreement warrants the complete list in its annex. - The instance names itself from inside the app — its display name, its logo, and the security contact
/.well-known/security.txtpublishes now live on the admin page under Instance identity, where an admin edits them without a deploy. They are stored beside the theme library, so a backup carries them and a restore brings them back. - A registry turns on from the app, not from the env file — the groups membership file, the service-account registry, the destination registry and the onboarding questions each sit beside the state plane by default. An admin adds the first group, service account or delivery channel on the admin page and the feature starts working. Previously each one existed only if the operator had first pointed a variable at a path and restarted, while the editor for it sat in the app saying it was not configured.
- Operations — this release renames, removes and adds environment variables. Read this before you update. Every change is one edit to the env file. - Added, and required:
ARKUSH_IDENTITYstates how the deployment authenticates people —signin(the service signs people in over Google),proxy(an identity front sets an email header the service trusts), ordev(every tokenless caller is one address, localhost only). The service refuses to boot without it. Set the one that matches what the instance already does. Every other identity variable now belongs to exactly one mode, and one set under another mode refuses the boot by name:ARKUSH_SIGNIN_GOOGLE_CLIENT_ID,ARKUSH_SIGNIN_GOOGLE_CLIENT_SECRETandARKUSH_SIGNIN_ALLOWbelong tosignin,ARKUSH_AUTH_HEADER,ARKUSH_LOGIN_URLandARKUSH_LOGOUT_URLtoproxy, andARKUSH_DEV_USERtodev. - Added:ARKUSH_GROUPSpicks the groups backend —file(the default, the membership file admins edit in the app) orgoogle(the registry of Google group addresses). It replaces the rule that set one of the two file variables and left the other unset. - Renamed:ARKUSH_OAUTH_CLIENT_ID→ARKUSH_BROWSER_GOOGLE_CLIENT_ID,ARKUSH_GOOGLE_API_KEY→ARKUSH_BROWSER_GOOGLE_API_KEY,ARKUSH_GOOGLE_PROJECT_NUMBER→ARKUSH_BROWSER_GOOGLE_PROJECT_NUMBER, andARKUSH_BQ_BILLING_PROJECT→ARKUSH_BROWSER_BQ_PROJECT. The four are the browser's own Google coordinates, and the old names did not say so — one of them read as a pair with the server's sign-in client, which it never was. - Removed:ARKUSH_INSTANCE_NAME,ARKUSH_LOGO_URLandARKUSH_SECURITY_CONTACT— set them on the admin page after the update, once.ARKUSH_HANDBOOK_DIRis gone too; nothing set it. - A stale line refuses the boot. Every renamed and removed name above is refused by name at startup, and the refusal says what replaced it. An env file that keeps one does not start the service rather than running with a setting that means nothing — the failure this release exists to remove. - Changed meaning:ARKUSH_GROUPS_FILE,ARKUSH_GOOGLE_GROUPS_FILE,ARKUSH_SERVICE_ACCOUNTS_FILE,ARKUSH_DESTINATIONS_FILEandARKUSH_ASK_FILEnow only move a file that already has a place beside the state plane. An instance that sets one keeps working. An instance that sets none gets every registry, empty. A variable that names a path holding no file refuses the boot, because a stated location with nothing at it is a typo rather than an empty registry. - Every server-side warehouse query now runs as a named service account, so Google decides who may spend it. A datasource could leave the account blank and run under the deployment's own identity instead, which reads every dataset that identity was ever granted — and the analysts allowlist handed that whole reach to everyone on it. Blank is no longer a way to run: a datasource names an account an admin registered, and Google's own permissions on that account say who may use it. Derived views, uploaded files and addressed data are untouched, and a query under your own Google still runs in your browser.
- Operations — read this before you update: dashboards whose datasources name no account stop refreshing. For each one, set "Runs as" on the datasource to a registered account and run it once; the schedule resumes from there. Two consequences worth knowing: the analysts allowlist no longer opens the warehouse, so a person who should keep running queries needs the Service Account Token Creator role in Google Cloud instead — and the service's own identity now needs no BigQuery access at all, so you can take that grant off it.
- Agents — a warehouse datasource must name `runsAs`, and the catalog tools require `service_account`.
run_datasourceon abigquerysource with norunsAsanswersforbiddenand names the picker that sets one.list_bigquery_tablesandget_bigquery_tabletake the account as a required argument —list_service_accountsnames the registered ones, and per-account catalogs differ, so the answer is that account's view of the warehouse. Browsing under the deployment's own identity is gone. The authoring guide says so. - A schedule stops when the person behind it loses access. A scheduled refresh runs on somebody's authorization: the person whose run last blessed the datasource. That authorization used to outlive them, so a departed analyst's dashboards kept refreshing under an account they could no longer use themselves. The sweep now re-checks that person before every run, and pauses the datasource when Google no longer grants them the account. Removing someone in Google Cloud is what stops their scheduled spending, and it takes effect within the hour. The sweep's own report lists every datasource it stopped and why, under
unauthorized, naming what the person is missing and, where that is a Google role, the command that grants it back; each refusal also writes a log line naming the document, the datasource and the person. The dashboard itself still reads as scheduled until somebody acts on that report — a screen for it is not built yet. Anyone who may still run the datasource restarts its schedule by refreshing it once. - A snapshot says who refreshed it, and what it ran as. The two used to share one field, so a refresh under a service account showed the account's address where a person's name belongs — and left nobody to check when the schedule ran itself. Now the provenance line names the person, and the snapshot records the account beside it — which is what the server's refresh notice reports back and what agents read. A dashboard saved before this release is migrated as it is read.
- Agents — a snapshot answers `runsAs` beside `refreshedBy`.
get_dashboardandrun_datasourcereport the person the run was made by inrefreshedBy— your grant's consenting account, where you ran it — and the credential it spent inrunsAs, present only when the run named one. Previously a service-account run reported the account inrefreshedByand reported the person nowhere. - Operations — the stack names the image it runs, and every install sets one more line. The base compose file used to build the software from a source tree, which an install does not have. It now runs the image
ARKUSH_IMAGEnames, and compose refuses to start when that line is missing. Put the image and its version tag in the env file:ARKUSH_IMAGE=<image>:v<version>. Pin the version tag rather thanlatest, which moves to another version at the next recreate. The image carries the compose file itself, so an install that holds the image and the deploy guide holds everything it needs to start. To build from a source tree instead, add-f deploy/build.override.ymlto the compose command.