38 KiB
Finish list
Running list of things intentionally left unfinished by a Phase B/C step, so D0 (or an earlier owner named below) can pick them up. Remove a line once it's done.
-
B3 (tailscale):
ManagerCardShell/ManagerSearch-equivalent presentation is duplicated asTailscaleManagerShell/TailscaleManagerSearchinplugins/tailscale/src/frontend/TailscaleManagerShell.tsxrather than shared with host-metrics's originals. Promote a shared version into@termix/plugin-sdk/uionce a second manager-card plugin needs one, per the contract's "add to the SDK when a second plugin needs it" rule. Owner: D1. -
B5 (proxmox):
runAdaptivePollingandcnare duplicated inplugins/proxmox/src/frontend/stats/rather than shared with core'ssrc/ui/lib/originals.useConnectionRetry(the third duplicate B5 left here) is resolved: B6 promoted it into@termix/plugin-sdk/frontendonce file-manager became its fourth caller (after proxmox, docker, host-metrics, remote-desktop) and switched proxmox's own copy over too.runAdaptivePollingandcnare exported from@termix/plugin-sdk/uinow (B9), and host-metrics uses them there (B16); only proxmox's copies are left to switch. Owner: D1. -
B5 (proxmox):
runDueProxmoxAutoSyncsinplugins/proxmox/src/backend/routes.tsstill reachescreateCurrentPluginSettingsRepository/createCurrentHostRepositoryby relative import instead ofctx, because it needs to scan every host's Proxmox settings across every user before it knows which actor to run each sync as, and neitherctx.settingsnorctx.hostsexpose a cross-user bulk read today. Actx.settings.listHostsWithKeyor similar SDK addition would close this, if a second plugin's background scan ever needs the same shape. Owner: D1. -
B6 (file-manager):
plugins/file-manager/src/frontend/components/PdfPreview.tsxruns pdfjs without a worker (main-thread parsing) becausetermix-plugin build's esbuild step has no static-asset-copy pipeline the way core's Vite build resolves a?urlimport to a hashed/assets/file. Fine for the small files this preview usually opens; restoring a real worker needs the CLI (packages/plugin-sdk/cli/commands/build.mjs) to gain a way to emit an extra file intodist/and have the frontend reference it by its served/plugin-assets/<id>/path. Owner: D1, or whichever step first needs a worker or other static asset from a plugin bundle. -
B6 (file-manager):
transfer-tuning.ts's adaptive-tuning profile still persists to a JSON file underDATA_DIRrather thanctx.kv, andHostFilesTab.tsx'st()calls plus the manifest'scontributes.settings.hostlabelKeys deliberately point at two different key paths (hosts.*andsettings.host.*) for the same three fields, both carrying the same English text - a minor duplication from keeping the host editor's exact existing section verbatim (registerHostEditorSection) instead of switching it to the schema-driven settings form. Neither blocks anything; flagging for whoever next touches this plugin's settings UI. -
B6 (file-manager):
TabContext.tsx'scomputeUniqueTitlecomparestabType === "file_manager"(an activity-log type string, not a tab type - the real tab type has always been"files"), so that branch has never matched and silently falls through to the terminal title. Pre-existing, unrelated to the conversion; noted rather than fixed per the lean-mode rules. Owner: whoever next touches tab title logic. -
B6 (file-manager):
enable_file_manager,default_pathandscp_legacyare still livessh_datacolumns; only the copy-into-plugin- settings migration shipped (file-manager-settings-migration.ts). Every core read site the earlier exploration found (host.ts,host-normalizers.ts,host-bulk-routes.ts,credentials.ts,host-internal-routes.ts,database.ts's encrypt/decrypt round trip,plugins/host-view.ts,ctx-hosts.ts, plus the frontend'sHostEditorData.ts/HostManagerData.ts/SidebarTree.tsx/quick-connect mapping) still reads the column directly rather thanctx.settings.getHost. Switching every one of those and then dropping the column in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate) is real, separate work this step didn't attempt, since none of those reads block the plugin conversion itself. Owner: a dedicated follow-up step, or D0. -
B7 (tunnels):
enable_tunnelandtunnel_connectionsare still livessh_datacolumns; only the copy-into-plugin-settings migration shipped (tunnels-settings-migration.ts), and the plugin reads its host settings now. Core still carries the columns through the host routes, the encrypt/decrypt round trip,SidebarTree.tsx's host duplicate payload, the quick-connect mapping and theHost/SSHHosttypes, none of which the plugin reads any more. Dropping them in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate) is the same follow-up the B6 file-manager line above describes. Owner: a dedicated follow-up step, or D0. -
B7 (tunnels): host export/import, host duplicate, the Hosts panel feature filter and the bulk enable/disable menu all work off
ssh_datacolumns, so none of them can see a plugin's host settings. For tunnels the "Tunnel" filter and the bulk enable/disable entries were removed, and the export's "Tunnels" group became "Proxy" (SOCKS5 fields only) sincetunnelConnectionsthere is now stale. B15 removed the "Docker" filter and the bulk Docker toggles the same way. Bringing these back needs a way for core to read and write host-scope plugin settings generically (keyed offcontributes.settings.hostorhostCapability) rather than per column. Owner: D1. -
B7 (tunnels): the tunnel tab no longer writes a recent-activity entry:
logActivityis a core@/main-axioscall and the plugin imports nothing from@/. Old "tunnel" entries still reopen the tab through the plugin'sactivityTypes. Anapp.logActivity(or equivalent bridge member) would restore it for this plugin and let file-manager drop its own@/main-axiosimport for it too (host-metrics and docker use thelogActivitythe ui entry exports). Owner: D1. -
B7 (tunnels): on the desktop app, tunnel statuses from a connected remote server are no longer merged into the tab: the old code polled the remote tunnel service through
getRemoteTunnelApi(), and the SDK has no remote-origin plugin API client.subscribeTunnelStatusesalready takes afetchRemoteand merges with local statuses winning, so it only needs a client for/plugin-api/tunnels/statuson the remote server. Owner: D1. -
B8 (web-endpoint):
enable_web_uiandweb_ui_configare still livessh_datacolumns; only the copy-into-plugin-settings migration shipped (web-endpoint-settings-migration.ts). Unlike B6/B7, this step DID convert every core read/write of those two fields (host.ts's create/update and export routes,host-normalizers.ts,host-bulk-routes.ts,database.ts's encrypt/decrypt round trip is the one exception - it copies whatever is already in the column for the raw DB export/backup tool, which needs no change since nothing reads that copy throughpluginSettings). Dropping the columns in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate) is the only remaining piece. Owner: a dedicated follow-up step, or D0. -
B8 (web-endpoint): added the first generic cross-plugin hook for core routes that touch host-scope plugin settings without importing a plugin:
ctx.registry.provide("<id>.hostImportNormalizer", fn)plusapplyPluginHostImportSettingsinhost-plugin-settings.ts, called fromhost-bulk-routes.ts's Termix-JSON import path. Only import validation is covered; B7's TODO above about the Hosts panel feature filter and the bulk enable/disable menu (hostCapability-driven, not import-driven) is unrelated and still open. Owner: D1 for extending the same pattern there. -
B8 (web-endpoint): noticed while sanity-checking with
npm run lint(not required by this step):eslint.config.mjs'sglobalIgnores(["dist", ...])only matches a top-leveldist/folder, not nestedpackages/*/distorplugins/*/dist, sonpm run lintfails on bundled output repo-wide with "Definition for rule X was not found" errors. Pre-existing, not introduced by this or any single plugin step. Owner: whoever next needs a cleannpm run lint, or D0. -
B8 (web-endpoint): the isolated-window Electron IPC bridge (
src/backend/utils/electron-ipc-bridge.ts,ctx.desktop.openIsolatedWindow) is a single request/response channel keyed by a random id with one registered backend-request handler (open-isolated-window) inelectron/main.cjs. Fine for today's one caller; if a second plugin needs to ask Electron's main process for something, extendBACKEND_REQUEST_HANDLERSthere rather than building a parallel channel. No owner needed unless a second caller appears. -
B9 (ssh-terminal), for D2: the terminal socket is a public route with
optionalAuth, because share-link and room guests authenticate inside the handler with their token. Guest auth itself is unchanged; review it with the rest of the public routes. -
B9 (ssh-terminal): recording has no provider yet. The terminal calls the optional
recordings.writerv1open(meta)and gets a sink withappend(chunk)(one call per 300 ms batch, the first starting with the asciicast header),persist(summary)anddiscard(). Without it nothing is recorded and nosession_recordingsrow is written. Owner: B13, which writes the file underDATA_DIR/session_logs/<user>/<session>.cast(mkdir and writeFile on the first append, appendFile after, never per chunk, see issue #1049) and creates or updates the row as the old session manager did. -
B9 (ssh-terminal):
enable_terminal,enable_terminal_toolbarandenable_command_historyare still livessh_datacolumns. The copy into ssh-terminal host settings shipped (ssh-terminal-settings-migration.ts) and the terminal reads its settings, but core host routes, types, the export/import sample andHostEditorDatastill carry the columns. B14 gave remote desktop its ownenableToolbarhost setting, copied fromenable_terminal_toolbar, so nothing else holds the column up. Drop all three the B14 way (see "Order at boot" in ARCHITECTURE.md). Owner: D0. -
B9 (ssh-terminal): the legacy
settingsrows for the terminal (terminal_session_timeout_minutes,terminal_session_persistence_enabled,command_history_enabled,touch_input_settings,terminal_image_*) are left in place for a release after the move; nothing reads them. Delete them in 3.0.0.sessionPersistenceis carried over but, as before, nothing acts on it. Owner: D0 to decide. -
B9 (ssh-terminal): the image storage paths used to be rejected at save when not absolute; the schema-driven settings form accepts any string and the resolver ignores a bad one with a warning. A custom field or a validate hook on plugin settings would restore the save-time error. Owner: D1.
-
B9 (ssh-terminal):
@termix/plugin-sdk/uinow re-exports a handful of core APIs the terminal needs (logActivity,getHostPassword,patchOpenTab,getUserPreferences,parseCustomKeybindings,setHostAutoTmux,getCookie) and the connection helpers. They belong on the typed host bridge, which is also where B7'sapp.logActivityline above lands. Owner: D1. -
B9 (ssh-terminal):
OPKSSHDialogis exported from@termix/plugin-sdk/uiso the terminal can show the opkssh sign-in. It leaves the ui entry when opkssh becomes a plugin that draws its own UI interminal.overlay. Owner: Phase C (opkssh). -
B9 (host-metrics): the terminal toolbar's CPU/memory/disk bars (
TerminalMetricsStatus) poll the local backend only; on the desktop app a host that lives on a connected remote server shows no bars. Same fix as the B7 remote-origin line above. Owner: D1. -
B9: the Hosts panel's "Terminal" feature filter was removed along with the column reads, the same as B7's tunnel filter. Owner: D1, with the generic host-settings filter.
-
B9: the startup purge of expired open tabs in
db/index.tsreadterminal_session_timeout_minutesfrom core settings; it was removed and expired tabs are dropped when the tab list is read, against the terminal's own timeout (GET /open-tabs/session-timeout, 30 while the terminal is off). No owner unless the table growth matters. -
Pre-existing (B8), found in B9:
node scripts/check-shell-plugin-ids.cjsfails onsrc/ui/lib/host-to-ssh-host.tsspellingweb-endpoint(thepluginSettingslookup B8 added). Not allowlisted and not introduced here. Owner: D0 (read it through a registry or allowlist it with a reason). -
B10 (tmux-monitor):
enable_tmux_monitoris still a livessh_datacolumn; only the copy-into-plugin-settings migration shipped (tmux-monitor-settings-migration.ts), the same as B6/B7's own columns. Every core read site (host.ts,host-normalizers.ts,host-bulk-routes.ts,database.ts's encrypt/decrypt round trip) still reads the column directly. Dropping it in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate) is the same follow-up the B6/B7/B8 lines above describe. Owner: a dedicated follow-up step, or D0. -
B10 (tmux-monitor):
terminalToolbar.openTmuxMonitorandterminalToolbar.tmuxDetachare dead locale keys (nothing callst("terminalToolbar.openTmuxMonitor")ort("terminalToolbar.tmuxDetach")anywhere in the tree; the terminal's own live detach button usesterminalToolbar.detachTmux/detachTmuxDescriptioninstead). Pre-existing, not introduced by this step; sibling dead keys (copyTerminalUrlAction/copyDockerUrlAction/copyHostMetricsUrlAction,terminalUrlCopied/dockerUrlCopied/hostMetricsUrlCopied) are the same vintage, from before the generichosts.copiedToClipboard/copyViewUrlActionreplaced them. Owner: whoever next cleans upsrc/ui/locales/en.json, or D0. -
B10 (tmux-monitor): no plugin contributes an "Open Tmux Monitor" (or "Open Docker" / "Open Tunnel" / "Open Host Metrics") quick link into
terminal.toolbartoday; that whole quick-link row was dropped when the toolbar was rebuilt as a generic action slot (aiis the only realterminal.toolbarcontributor, for its assistant button) and never replaced per-feature. Adding tmux-monitor's own link is straightforward (app.registerSlotContribution("terminal.toolbar", ...)plusapp.registerActionopening the singleton tab) but is a shared gap across four plugins, not specific to this step. Owner: D1, or whoever revisits the toolbar's quick-link row. -
B11 (serial):
plugins/serial/src/frontend/Serial.tsxuses a small hardcoded light/dark xterm palette and a generic monospace font stack instead of the app's real terminal theme system (resolveTermixThemeColors,DEFAULT_TERMINAL_CONFIG,TERMINAL_FONTS, font loading), which is still core-only (src/ui/features/terminal/,src/ui/lib/terminal-themes.ts) and not yet exposed through the SDK. The serial console therefore does not match a user's chosen terminal theme or font. Promoting that system into@termix/plugin-sdk/uiis really ssh-terminal's own Phase B step's call to make (it is the plugin that currently owns the terminal component itself, per the "Known specifics" note above), so this line just points at the gap rather than building a premature shared surface for one caller. Owner: ssh-terminal's own Phase B step, or D1. -
B11 (serial): shipping a plugin's own native dependency (
serialport) works for this repo's three bundled deployments only because they all install the whole npm workspace at once and copy/rebuild one sharednode_modules- see "Native dependencies" in ARCHITECTURE.md for the full reasoning. A community plugin installed later from a tarball has no such sharednode_modulesto land in, so it cannot ship a native dependency the same way. No fix attempted here; the honest workarounds are listed in that section. Owner: whoever designs the 3.0.0 install flow (prebuild-per-platform packaging, or a documented "avoid native code" rule for community plugins). -
B12 (session-sharing):
allow_session_sharingis still a livessh_datacolumn. The copy into the plugin's host setting shipped (session-sharing-settings-migration.ts) and nothing in core reads or writes it any more; drop it in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate). Owner: D0. -
B12 (session-sharing): the legacy
session_sharing_globally_enabledsettings row is left in place for a release; nothing reads it. Delete it in 3.0.0. Owner: D0 to decide. -
B12 (session-sharing): room audit lines go through
ctx.audit.record, which records the actor but not the request's IP address and user agent the oldlogAuditcalls carried. Adding those toctx.audit.recordwould restore them. Owner: D1. -
B12 (session-sharing): the share button lives in the terminal and remote desktop toolbars now; the tab bar's share button is gone, so a host with the terminal toolbar switched off has no share button. A generic tab-menu slot would bring it back if that matters. Owner: D1, if wanted.
-
B13 (session-recording):
enable_session_loggingis still a livessh_datacolumn; only the copy-into-plugin-settings migration shipped (session-recording-settings-migration.ts). The legacy default wastrue; the plugin's enable switch defaults tofalselike every other plugin's, so the migration copies every host's value rather than only the ones that turned it off - a fresh host created after 2.9.0 defaults to recording off, a real behaviour change from the old column default, made once here rather than silently.ssh-terminal/src/backend/terminal-socket.tsstill readsresolvedHostData?.enableSessionLogging ?? hostConfig.enableSessionLoggingoff the resolved SSH host instead ofctx.settings.getHost(remote desktop asksrecordings.writer.enabledForsince B14), andHostEditorData.ts,HostManagerData.ts,quick-connect-host.tsand the export/import sample all still read the column directly. The General tab's checkbox was removed fromHostEditor.tsx(the schema-driven Plugins group replaces it); the data plumbing above was not switched over. Dropping the column in lockstep (schema.ts,db/index.ts, a drizzle migration per dialect,schema:generate) needs every one of those switched first. Owner: a dedicated follow-up step, or D0. -
B13 (session-recording):
accessId(the legacyaccess_idcolumn, referencing the now-core-onlyhost_accesstable) is carried in the adopted table's shape as a plain unreferenced integer, since the SDK'sdefineTablehas no builder for a foreign key into an arbitrary core table beyondrefUser()/refHost(). Nothing has ever populated it (true in core before this step too). Owner: none needed unless a future step wants to actually use it. -
B14 (remote-desktop): the moved columns stay in the database, unused:
ssh_data.enable_rdp,enable_vnc,enable_telnet,rdp_port,vnc_port,telnet_port,rdp_security,rdp_ignore_cert,security,ignore_cert,guacamole_config, anduser_preferences.rdp_defaults. Their drizzle migrations areSELECT 1;on purpose (the copy runs after drizzle on Postgres and MySQL). Drop them physically in 3.0.0, once every install has booted 2.9.0. The legacyguac_enabledandguac_urlrows insettingsare left too. B15 did the same withssh_data.enable_dockeranddocker_config(copied bydocker-settings-migration.ts). Owner: 3.0.0. -
B14, found in B5 (proxmox):
drizzle/postgres/0036_redundant_ender_wiggin.sqlanddrizzle/mysql/0035_clumsy_leech.sqlreallyDROP COLUMNthe four proxmox host columns. On Postgres and MySQL that runs at boot beforerunProxmoxSettingsMigrationcopies them, so an upgrade there loses every host's Proxmox settings. Since nothing has shipped, rewrite those two files to theSELECT 1;pattern (keeping their journal entries). Owner: D0. -
B14, found in B5 to B13: every earlier host-column copy (
proxmox,file-manager,tunnels,web-endpoint,ssh-terminal,tmux-monitor,session-sharing,session-recordingsettings migrations) reads the old columns withgetDb().all(sql...), which only exists on SQLite. On Postgres and MySQL the call throws, the migration's catch logs a warning and nothing is copied. Switch them toselectRowsfromutils/crypto-migration/raw-rows.ts, which B14 added and uses. Owner: D0. -
B14, found in B13:
session-recording-settings-migration.tswritesrow.enable_session_logging !== false, but SQLite returns0, so a host that had recording off migrates as on. Its test only uses real booleans. Owner: D0. -
B14 (remote-desktop), for D2: host payloads carry every plugin's host settings after the sharing sanitizers, so a connect-level recipient now receives remote desktop's
guacamoleConfig, which can hold an RDP gateway password in plain text. A view-level recipient saw it before B14 too. Decide whether plugin host settings need a per-field sharing level, or make the gateway password its own secret field. Owner: D2. -
B14 (remote-desktop): core still holds some locale keys that look dead after this step but may be built dynamically:
hosts.guac.noCredential,authType*,recording*,saveHostFirst,sharingOptionsAfterSave,permissionLevel,typeHeader,targetHeader,permissionHeader,hosts.connectRdp/Vnc/Telnet,hosts.copy*UrlAction,hosts.telnetandhomepage.connType_*. Owner: whoever next cleans upsrc/ui/locales/en.json, or D0. -
Pre-existing, found in B14:
npm run test -w plugins/web-endpointfails "answers 503 while the tunnels plugin is not available": the route expectsctx.services.getto throw for a missing service, but since B12 the runtime and the test doubles hand back an empty handle, so the route calls an undefinedforwardand answers 500. Checktypeof forwardthe way remote desktop does. Owner: D0. -
Pre-existing, found in B14: each plugin's own
typecheckscript (tsc -p tsconfig.json) fails with the SDK'stsconfig.plugin.json(node16 resolution wants.json relative imports and cannot resolve@termix/plugin-sdk/ui), workspaces included.npm run type-checkcovers plugins throughtsconfig.plugins*.jsonand passes. Owner: D0. -
B16 (host-metrics): Termix-Mobile read
statsConfigon each host (statusCheckEnabled,enabledWidgets) and the host status from the metrics API. The host payload now carriesstatusCheckEnabledandstatusCheckIntervalat the top level, the metrics options underpluginSettings["host-metrics"], and statuses come fromGET /host/status. The mobile app needs the matching change, or the host payload astatsConfigbuilt from those fields for one release. Owner: D1. -
B16 (host-metrics): a host's status is only checked once its owner has asked for statuses (opened the app) since the server started: a user who only has a host shared with them sees no dot for it until then. This was already true before B16 (the first user to load the host list started polling for their own hosts only). Starting checks for the owners of every host a requester can see would fix it. Owner: D1.
-
B16 (host-metrics): the host JSON export does not carry host-metrics's host settings (it did carry
statsConfig); import reads both the oldstatsConfigandpluginSettings["host-metrics"]through the plugin's import normalizer. Same gap as the B7 line about exporting plugin host settings generically. Owner: D1. -
B16 (host-metrics): the CPU and network collectors keep their last sample per host in module-level maps (
widgets/cpu-collector.ts,widgets/network-collector.ts) to compute rates. The poller clears them on deactivate and per host, so nothing leaks, but they are still module state rather than per-activation. Owner: D1. -
B15 (docker), for D2: the console socket
/plugin-ws/docker/consoleis a public route withoptionalAuth. The handler refuses a socket with no signed-in user or withoutdocker.useand checks the data key per connect message, but review it with the rest of the public routes. -
B15 (docker):
ssh_data.show_docker_in_sidebar(and its siblings for terminal, files, tunnels and server stats) are dead columns nothing reads; only the raw database export still copies them. Drop them the B14 way. Owner: D0. -
B15 (docker): the Docker manager's card or table layout is still an override on core's Appearance presets (
types/ui-preferences.tsdeclares adockerarea, as it doeshostMetrics), reached through the ui entry'suseAreaPreferencesanduseUiPreferencesContext. A plugin cannot declare its own area yet. Owner: D1. -
B15 (docker): the raw SQLite database import (
database.ts) no longer bringsenable_dockeracross, the same gap every earlier column move left there: it writes host rows only and never plugin settings. Owner: D1, with the generic host-settings export and import. -
B17 (automations), for D2: the ctx table says
ctx.events.onneedsevents:corefor core topics, but the runtime only checksemit. docker, file-manager, host-metrics, session-recording and snippets subscribe to core topics (host.*,user.*) without declaring it. Either gateonand addevents:coreto those five, or change the doc to say subscribing is ungated. Owner: D2. -
B17 (automations): the 2.8 webhook URL
/automations/webhook/<token>is kept only by a rewrite indocker/nginx.confanddocker/nginx-https.conf. An install that serves the backend without nginx (the desktop app's embedded backend, a hand-rolled proxy) answers 404 there. Decide whether that matters enough for a generic legacy-path alias in the plugin HTTP layer. Owner: D0. -
B17 (automations): there are two notification senders now:
deliverNotification(ctx.notify, per-channel private opt-in, throws) and the oldersendWebhook/sendNtfy/sendDiscordthat the channel "test" route innotification-channels-routes.tsstill uses (always applies the allowlist, retries once). Point the test route atdeliverNotificationand delete the old ones. Owner: D1. -
B17 (automations): the scheduler no longer skips a user whose data key cannot be resolved (
DataCrypto.canUserAccessDatahas no SDK equivalent). With system-wrapped DEKs that check was effectively always true; if it ever is not, the run now fails on its first SSH step instead of being skipped silently. Owner: none unless a background "is this user unlocked" check is added to the SDK. -
B18 (ai): the "allow read-only diagnostic commands" user setting only adds a line to the system prompt. No tool runs a command directly;
tools/command-allowlist.ts(isReadOnlyCommand) is tested but unused, so every command is still a proposal. Either add the direct tool or drop the setting. Owner: D0. -
B18 (ai): provider requests now go through
ctx.fetch, which has no outbound proxy support. Before, an admin-allowlisted private provider went throughfetchWithProxy(public ones never did). Decide whetherctx.fetchshould honour the proxy settings. Owner: D0. -
B18:
scripts/generate-dialect-schema.cjsdecides varchar or text by column name across every table, so removing the ai indexes oncreated_at,updated_atandlabelturned those columns intotexteverywhere inschema.pg.tsandschema.mysql.ts. drizzle-kit then wrote about a hundred type changes, which B18 left out ofdrizzle/postgres/0048anddrizzle/mysql/0047(both a no-op), so real databases keepvarchar(255)while the snapshots saytext. Key the generator per table, regenerate, and checkverify:dialect. Owner: D0. -
B18: saving a plugin setting from the Settings screen tells nobody. The ai frontend re-reads its status on
termix:plugins-changedand on window focus (throttled to 30 seconds), so an admin turning it on shows up on the next focus. A settings-changed hook on the app object would be cleaner. Owner: D1. -
B18: tunnels builds its own SSE URL off the axios base instead of the new
app.fetch. Owner: D1. -
Pre-existing, found in B19:
createMockCtx'swsRoutesdouble (B15) records{ path, raw, handler, options }per route, butplugins/serial/tests/backend/activate.test.ts,plugins/ssh-terminal/tests/backend/activate.test.tsandplugins/tunnels/tests/backend/activate.test.tsstill assert the old two-field shape ({ path, raw }) and fail ontoEqual. Not touched by this step; update each assertion to match or to check only the fields it cares about. Owner: D0. -
Pre-existing, found in B19:
src/backend/hosts/file-manager/(21 files) is dead code left over from before the file-manager plugin (B6): nothing insrc/backend/starter.tsor elsewhere importshosts/file-manager/index.ts, so its own port (30004, still listed insrc/backend/utils/swagger.ts's server list) never actually starts and itsaxios.post("http://localhost:30006/activity/log", ...)calls (now a dead port after this step) never run. Delete the directory once confirmed nothing else references it. Owner: D0. -
Pre-existing, found in B19:
src/ui/main-axios.ts'stmuxMonitorApistill points atgetApiUrl("/tmux_monitor", 30010), a port tmux-monitor's own B10 step moved off of (perplugins/tmux-monitor/CHANGELOG.md) without updating this frontend constant to/plugin-api/tmux-monitor. Not something this step touched or verified further. Owner: D0. -
Pre-existing, found in B20:
npm run test:pluginsfails on three tests unrelated to wake-on-lan or secret-sources:ssh-terminalandtunnelsboth assertmock.wsRoutesequals[{ path, raw }], but the mock now also carrieshandlerandoptions(added for B15's doubles, per the contract's "Tests" section), so the exact-equality assertion fails; andweb-endpoint's "answers 503 while the tunnels plugin is not available" test gets 502. Confirmed present ondev-2.9.0before this step (checked by stashing B20's changes and re-running). Owner: D0. -
C1 (totp):
totp-migration.tsmoves a user's TOTP secret only when their data key opens at boot. A 2.8 user whose key is still a pre-2.5.1 password wrap keeps their enrolment row (fail closed) but has no secret inp_totp_enrollmentsuntil a boot after their next password login migrates the key, so that one login fails at the TOTP step. Likewise, enabling the totp plugin for the first time without a restart does not move secrets until the next boot. Decide whether either needs handling (a migration run on plugin activation, or an admin reset message). Owner: D0. -
C1 (totp/webauthn): the
userscolumnstotp_secret,totp_enabledandtotp_backup_codesstay in the database (dropped fromschema.ts, the drizzle drop is a no-op) until 3.0.0 removes them, after the boot migration has run everywhere. Owner: 3.0.0. -
C2 (sso/ldap):
users.is_oidc,oidc_identifier,sso_provider_idand the 2.8 per-user OIDC columns (client_id,issuer_url, ...), andsessions.sso_provider_id,oidc_sub,oidc_sid, stay in core. Core still readsis_oidc/oidc_identifierfor account linking,/users/meand the$oidc.preferred_usernameplaceholder, and still writes them from an identity'slegacyfield so a downgrade works. Rename or drop them once nothing needs the 2.8 shape. Owner: 3.0.0. -
C2 (sso/ldap):
database/routes/auth-compat-routes.tskeeps the 2.8 URLs (/users/oidc/*,/users/oidc-config,/users/sso-providers,/users/ldap/login) and so names theoidc/ldapmethod ids and the sso plugin's/plugin-api/sso/paths. Keep until identity providers have moved to the new redirect URI and Termix-Mobile uses/users/auth/*. Owner: 3.0.0. -
C2 (sso): the
oidc_auto_provisionandoidc_silent_login_defaultsettings, their routes and admin toggles are generic (every external method, every redirect method) but still carry OIDC in their names and i18n. Rename with a settings migration if wanted. Owner: D0. -
Unrelated, found in C2:
node scripts/check-shell-plugin-ids.cjsfails onsrc/ui/lib/host-to-ssh-host.tsnaming"web-endpoint"(h.pluginSettings?.["web-endpoint"]). The file is untouched since before C2. Owner: D0.
Manual checks after 2.9.0
- SSH terminal: password auth, key auth (and an encrypted key's passphrase prompt), TOTP keyboard-interactive, a jump host, Warpgate, split view, snippets sent to a terminal, reconnect after a network drop and after a page reload (session reattach), and the local terminal in the desktop app.
- Serial: the Electron backend path against a real device path/COM port, and the Web Serial browser picker in Chrome or Edge.
- Session sharing: a link share (read-only and read-write) opened in a private window, a user share joined from Active Connections, revoke and "end for everyone", and a collab room with two members, control hand-off, a guest link, rotate and end.
- Session recording: an SSH session recorded end to end (playback matches what happened, including a resize mid-session), a host with recording switched off records nothing, retention prunes an old recording on schedule, a guacd RDP/VNC/Telnet recording still gets a row and plays back, and deleting a user anonymizes their recordings instead of removing them.
- Host status and metrics: on a 2.8 database, hosts whose status checks were off stay off and a custom interval survives; the host list dot goes reachable, then online once the Host Metrics tab or a terminal logs in; an RDP-only host on a custom port shows online; the metrics tab, the dashboard host bars, the homepage host status widget, the metrics chart widget, the terminal toolbar bars and the file manager's disk bar; a manager card (services, packages) and a health check; TOTP on the metrics connect; the admin metrics interval, history retention and new-host default; the temperature unit; disabling host metrics leaves every other plugin's SSH working, then enabling it again.
- Remote desktop: an RDP, a VNC and a Telnet host; a host behind one jump
host (tunnels plugin on and off) and behind a chain of two; macOS Screen
Sharing over VNC with no username; RDP recording (and none with
session-recording off or the host's switch off); file transfer through the
RDP drive, drag and drop included; clipboard both ways; sharing an RDP
session by link (read-only and read-write) and presenting it in a collab
room; opening Windows Remote Desktop from the desktop app on Windows;
changing the guacd URL in admin settings without a restart; turning Remote
Desktop off in admin (actions disappear, connects answer 403); a 2.8 host
that only had
connection_type = rdpcomes up as RDP with SSH off; RDP user defaults apply to a saved host; Quick Connect over RDP and VNC; the desktop app with a host on the remote server and one on This device. - Docker: a 2.8 host with Docker on (and one on Podman) keeps its switch and
runtime; list, start, stop, logs, stats and the console on a Linux host;
TOTP (including a wrong code first) and Warpgate on the connect; a host
with no stored password asks for credentials; the homepage Docker widget;
an automation Docker step and a
docker_eventtrigger; disable Docker with a console open, then enable it and open another. - Automations: on a 2.8 database the automations, their run history and
their channel links are there; a schedule fires; a webhook fires on both
/plugin-api/automations/webhook/<token>and the old/automations/webhook/<token>; a metric threshold on a host nobody has open still fires (headless viewers); adocker_eventtrigger and a Docker step; a notify step to webhook, ntfy and Discord channels, and to a LAN ntfy with the private opt-in plus the admin allowlist; disable Docker and the automation shows "Needs docker", its scheduled runs are recorded as skipped and the others keep running, then enable it again; the AI assistant lists and creates automations, and says they are unavailable with automations off. - Homepage: on a 2.8 database every widget, its layout and every service
link are there; add, move, resize, edit and delete a widget of each type;
the favicon, RSS, ping and custom API widgets against a real remote URL,
and each refuses a private/loopback target; the dashboard's Service
Links and Homepage Preview cards; the Dashboard/Homepage toggle and its
copy-link and open-full-view buttons;
?view=homepage; docker's, tunnels' and file-manager's own homepage widgets still work with the homepage plugin disabled; the AI assistant lists homepage items and says unavailable with homepage off; disable homepage in admin (the tab, its dashboard cards and the toggle disappear) then enable it again. - TOTP and passkeys: on a 2.8 database a TOTP user is still asked for a code (and a backup code works once); set up TOTP from Settings > Security, add a second device, regenerate backup codes, disable it; enabling signs out other sessions; "remember me" skips it next time; disable the totp plugin and a TOTP user's login is refused until an admin resets their factors; register a passkey, sign in with it with and without a PIN (no PIN still asks for TOTP), delete it; existing 2.8 passkeys still sign in.
- SSO and LDAP: on a 2.8 database every SSO provider and LDAP directory is
there, in Settings > Plugins > Single sign-on and > LDAP; an existing
provider still signs in through its identity provider without changing
anything there (its redirect URI shows
/users/oidc/callback); add the new redirect URI at the provider, switch it over and sign in again; a new provider, GitHub and Google; an existing SSO user and an existing LDAP user land in their old accounts; the env-configured provider; admin group and role map; back-channel logout from Keycloak ends the session; the desktop app's system browser login and Termix-Mobile's login; silent sign-in; a second factor after an external login with the admin setting on and off; disable each plugin and its buttons go away and its URLs stop signing anyone in, then enable it again.