Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
202 commits
Select commit Hold shift + click to select a range
af2cfeb
docs(linux): add the driver layer to the architecture guide
datlechin Oct 13, 2026
fe32cfd
docs(linux): rewrite the driver guide for contract v2
datlechin Oct 13, 2026
24c2d9e
docs(linux): document driver errors, timeouts and TLS remedies
datlechin Oct 13, 2026
90c670c
docs(linux): add per-engine driver pages
datlechin Oct 13, 2026
5ca1adb
docs(linux): record driver contract v2 in ADR 0016
datlechin Oct 13, 2026
0eb7864
fix(linux-clickhouse): refuse a result line that never ends
datlechin Oct 13, 2026
3798bd4
fix(linux-clickhouse): give up on an editor KILL after the grace
datlechin Oct 13, 2026
3d1f37a
fix(linux-clickhouse): send no further query once a call is stopped
datlechin Oct 13, 2026
5715010
fix(linux-clickhouse): look a mutation up in the table the save wrote
datlechin Oct 13, 2026
855e0c2
fix(linux-clickhouse): report an expired editor session as lost
datlechin Oct 13, 2026
c6f4a47
fix(linux-clickhouse): pin the DateTime output format
datlechin Oct 13, 2026
6e0e097
refactor(linux): give each window its own pending-edit trackers
datlechin Oct 13, 2026
6dda859
ci(linux): fail fast checks on thread_local! in app services
datlechin Oct 13, 2026
3c3aae4
fix(linux-editor): keep statements sqlformat would respace unformatted
datlechin Oct 13, 2026
47d59f4
fix(linux-postgres): tell a statement timeout apart by time, not text
datlechin Oct 13, 2026
200717c
refactor(linux-editor): map Run at Cursor through one statement helper
datlechin Oct 13, 2026
db0a29f
fix(linux-postgres): run every table definition query inside its read
datlechin Oct 13, 2026
e819a86
fix(linux-postgres): keep user SQL in stream_read from writing
datlechin Oct 13, 2026
681075a
fix(linux-mysql): refuse a TLS name sqlx cannot verify over TCP
datlechin Oct 13, 2026
8972e22
fix(linux-mysql): stop the editor's batch when its reader goes away
datlechin Oct 13, 2026
e6f985d
fix(linux-mysql): prepare every save step so a ';' cannot add one
datlechin Oct 13, 2026
ffbd309
fix(linux-editor): build a result page only when its line is shown
datlechin Oct 13, 2026
9e03061
fix(linux-core): export reals and array items at column precision
datlechin Oct 13, 2026
12ffe8f
fix(linux-core): neutralise formulas in CSV values of unplaced types
datlechin Oct 13, 2026
7b6b989
fix(linux-core): write integers and scaled decimals to Excel exactly
datlechin Oct 13, 2026
712bc27
fix(linux-core): escape pipes in Markdown export column names
datlechin Oct 13, 2026
9a1aca6
fix(linux-core): refuse a MySQL # comment in a raw filter or type
datlechin Oct 13, 2026
99aaf67
test(linux-tests): bound every conformance case and each running count
datlechin Oct 13, 2026
4482bdd
test(linux-tests): fail a row guard on a present row
datlechin Oct 13, 2026
8b8ccbe
docs(linux): say which suite flags no capability can check
datlechin Oct 13, 2026
1372e83
fix(linux-tests): run a fake's close behaviour once per object
datlechin Oct 13, 2026
7fc64e7
fix(linux-editor): enforce the 8 MB SQL file limit while reading
datlechin Oct 13, 2026
47e9d58
docs(linux): correct editor cancel, row-limit and stop claims
datlechin Oct 13, 2026
8dee1a9
docs(linux): warn that tokio-postgres logs editor SQL at debug
datlechin Oct 13, 2026
fd4fd89
docs(linux): state the session fakes exception and drop em dashes
datlechin Oct 13, 2026
f1044ac
fix(linux-mssql): read money exactly in the editor and query results
datlechin Oct 13, 2026
eb07487
fix(linux-mssql): bind char and varchar keys as varchar so saves seek
datlechin Oct 13, 2026
55ef948
test(linux-mssql): pin a stop after any answer as mid-token
datlechin Oct 13, 2026
6d38e44
fix(linux-sqlite): let Stop end a statement waiting for a lock
datlechin Oct 13, 2026
6fdcbc6
fix(linux-sqlite): keep the connection when SQLite rolled back a save
datlechin Oct 13, 2026
39f7d5a
fix(linux-sqlite): run statements before an unclosed comment alone
datlechin Oct 13, 2026
cae14ed
fix(linux-sqlite): write a NUL in a text literal through char(0)
datlechin Oct 13, 2026
e2b49fc
fix(linux-storage): keep an import from taking over saved secrets
datlechin Oct 13, 2026
3708ed1
fix(linux-ssh): leave every new SSH host key to the app's dialog
datlechin Oct 13, 2026
baef3b1
fix(linux): stop a cancelled connect from saving or prompting
datlechin Oct 13, 2026
bd547b6
fix(linux-session): end a Retry's password prompt with its session
datlechin Oct 13, 2026
96ee24a
fix(linux): close the session when its window closes
datlechin Oct 13, 2026
eedae9e
fix(linux-editor): keep a MySQL function against its paren in Format
datlechin Oct 13, 2026
1ccec23
test(linux-editor): cover Run at Cursor while a run is in flight
datlechin Oct 13, 2026
7cf534d
refactor(linux-editor): drop comments that restate the results code
datlechin Oct 13, 2026
037dba1
fix(linux-ssh): leave a config's StrictHostKeyChecking yes in force
datlechin Oct 13, 2026
350183d
fix(linux): take back a new entry saved as its connect is cancelled
datlechin Oct 13, 2026
31ab99f
fix(linux-storage): name an imported copy apart from the saved entry
datlechin Oct 13, 2026
df811ca
docs(linux): drop prompt comments that only restate the code
datlechin Oct 13, 2026
b7ec8dc
docs(linux): drop changelog lines for fixes to unreleased flows
datlechin Oct 13, 2026
b5fd89b
merge linux-1b/core-tests
datlechin Oct 13, 2026
8c665e3
merge linux-1b/pg-mysql
datlechin Oct 13, 2026
1fc7842
merge linux-1b/mssql-sqlite
datlechin Oct 13, 2026
1819072
merge linux-1b/clickhouse
datlechin Oct 13, 2026
d53fb03
merge linux-1b/docs
datlechin Oct 13, 2026
b033ca2
merge linux-1b/trackers
datlechin Oct 13, 2026
64ca6ea
merge linux-1b/export
datlechin Oct 13, 2026
7cd6b0c
merge linux-1b/editor
datlechin Oct 13, 2026
211c696
merge linux-1b/app-flows
datlechin Oct 13, 2026
be04899
build(linux): list the new app files in POTFILES.in
datlechin Oct 13, 2026
007d302
fix(linux): keep client library statement text out of the logs
datlechin Oct 13, 2026
7e476a3
fix(linux): stop a MySQL, SQL Server or SQLite run whose caller drops
datlechin Oct 13, 2026
676601c
refactor(linux-core): plan a save in the order its changes are listed
datlechin Oct 13, 2026
2ddb2fe
fix(linux): undo restores the prior edit and saves follow edit order
datlechin Oct 13, 2026
2dc6a3a
ci(linux): run the app's docker tests in the GTK image
datlechin Oct 13, 2026
22f3df5
fix(linux-editor): drop a closed editor's theme and font handlers
datlechin Oct 13, 2026
d778960
fix(linux-structure): free a closed structure tab and its theme handler
datlechin Oct 13, 2026
93a91ea
fix(linux-grid): free columns and cells with their column view
datlechin Oct 13, 2026
80f5931
fix(linux-filter): free the filter strip with its browse tab
datlechin Oct 13, 2026
a8c4a2e
fix(linux-tabs): free the workspace tab view and toast overlay
datlechin Oct 13, 2026
348ecbb
docs(linux): note the memory freed with closed tabs and grids
datlechin Oct 13, 2026
72c229b
fix(linux): resolve a commit MySQL keeps no record of as Unresolvable
datlechin Oct 13, 2026
dbb86ed
fix(linux-mysql): trust the system CA store under VerifyFull
datlechin Oct 13, 2026
fc0f23d
test(linux): let the dropped editor run case find each statement
datlechin Oct 13, 2026
7f28c22
refactor(linux): drop the CancelAcknowledgement phase no driver raises
datlechin Oct 13, 2026
9d590c9
fix(linux): read imported files through the capped reader
datlechin Oct 13, 2026
2bf4656
fix(linux-clickhouse): wait for a stopped query before the next run
datlechin Oct 13, 2026
b82654d
fix(linux-tabs): free pages made by the tab overview's new-tab button
datlechin Oct 13, 2026
66cdf84
test(linux-grid): prove a closed popover editor frees its cell
datlechin Oct 13, 2026
aa90a68
test(linux-filter): prove a Between rule frees its From and To entries
datlechin Oct 13, 2026
b0865bc
style(linux-tabs): shorten the overview create-tab comments
datlechin Oct 13, 2026
7468fdd
fix(linux): keep sqlparser's statement text out of the logs
datlechin Oct 13, 2026
43e460d
docs(linux): correct stale notes on imports, stops and close
datlechin Oct 13, 2026
6fb9fef
perf(linux-clickhouse): skip the wait after a stop the server answered
datlechin Oct 13, 2026
180f15b
merge linux-1b/undo
datlechin Oct 13, 2026
10a21ea
merge linux-1b/cycles
datlechin Oct 13, 2026
d1f77c9
merge linux-1b/followups
datlechin Oct 13, 2026
f327f8d
build(linux): list wave 2's app files in POTFILES.in
datlechin Oct 13, 2026
b8361b5
fix(linux-clickhouse): bound the wait for a stopped query by the grace
datlechin Oct 13, 2026
68f55f1
test(linux): cover touch restore on draft and cell undo and redo
datlechin Oct 13, 2026
fbe626e
ci(linux): give the app's docker tests a nextest group
datlechin Oct 13, 2026
7740f56
docs(linux): list every docker job in the agent guide
datlechin Oct 13, 2026
1a1e0ac
feat(linux): add a request sequencer for a view's own fetches
datlechin Oct 13, 2026
dc08a16
feat(linux-session): share one schema catalog per session
datlechin Oct 13, 2026
bd2e137
refactor(linux): let browse tabs own sequenced, cancellable fetches
datlechin Oct 13, 2026
23f5168
refactor(linux): let structure tabs read their own definition
datlechin Oct 13, 2026
63caf3e
docs(linux): describe the schema catalog and tab-owned fetches
datlechin Oct 13, 2026
4d03722
fix(linux): rebuild the grid when a table's columns change
datlechin Oct 13, 2026
2aba5de
test(linux): cover the signals the browse and structure tabs emit
datlechin Oct 13, 2026
9372326
fix(linux): read a table's columns from the server when its tab opens
datlechin Oct 13, 2026
dc0e172
fix(linux): keep a table tab's rows when a refresh of them fails
datlechin Oct 13, 2026
4be3ebf
style(linux): clear em dashes from window and structure comments
datlechin Oct 13, 2026
4a69ffe
fix(linux): drop a released session's queued sidebar and health updates
datlechin Oct 13, 2026
9f2d816
fix(linux): show the error page when a tab's filter no longer fits
datlechin Oct 13, 2026
72cc070
fix(linux): keep a structure tab's definition when a re-read fails
datlechin Oct 13, 2026
ec74c4a
fix(linux): ignore a table drop that lands after a connection switch
datlechin Oct 13, 2026
326b617
fix(linux): log a table list that failed to refresh once, not twice
datlechin Oct 13, 2026
5ca7c9d
docs(linux): list the reconnect recovery of table and structure tabs
datlechin Oct 13, 2026
83440de
style(linux): trim narrative comments in the window and tab code
datlechin Oct 13, 2026
b72ff6f
merge linux-1b/fetches
datlechin Oct 13, 2026
8b5372f
merge linux-1b/polish
datlechin Oct 13, 2026
d80dc10
refactor(linux-grid): move the data grid into a ui/grid module
datlechin Oct 13, 2026
bfd3fc4
refactor(linux): add a per-session workspace store with a writer task
datlechin Oct 13, 2026
76f4883
refactor(linux): save each session's tabs off the GTK thread
datlechin Oct 13, 2026
7ad8b07
fix(linux): write exported connections off the GTK thread
datlechin Oct 13, 2026
b1fc588
feat(linux-core): list completion keywords and bare names per grammar
datlechin Oct 13, 2026
7b0c53f
feat(linux-editor): complete tables, columns, keywords from the catalog
datlechin Oct 13, 2026
1f1a16f
fix(linux-core): quote the words each engine reserves, not completions
datlechin Oct 13, 2026
87efe15
refactor(linux)!: generate DDL through per-engine DdlDialect plans
datlechin Oct 13, 2026
bb21852
docs(linux): record DDL plans in ADR 0010 and the driver guide
datlechin Oct 13, 2026
7267319
fix(linux-editor): read only catalog names, skip FROM in calls
datlechin Oct 13, 2026
bd9690f
perf(linux-editor): read only the text near the cursor to complete
datlechin Oct 13, 2026
b072769
feat(linux-storage): find and move aside unreadable drafts
datlechin Oct 13, 2026
43ced9d
fix(linux): restore every saved tab and cap the tab file by size
datlechin Oct 13, 2026
5d728c5
fix(linux): save editor text on disconnect and after a reconnect
datlechin Oct 13, 2026
a8ec2f2
test(linux): cover structure tab saving and the detach timer
datlechin Oct 13, 2026
d6c5517
fix(linux): read and reset saved connections off the GTK thread
datlechin Oct 13, 2026
9393ef2
refactor(linux): drop the unused nullability_switch DDL capability
datlechin Oct 13, 2026
55f5ed9
fix(linux): save SQLite tables keyed by INTEGER PRIMARY KEY
datlechin Oct 13, 2026
0bbac45
fix(linux): script a PostgreSQL serial column as serial
datlechin Oct 13, 2026
9fb638a
fix(linux): restate MySQL column attributes and gate comment edits
datlechin Oct 13, 2026
c6d1273
fix(linux): run the Structure tab's checks on every save
datlechin Oct 13, 2026
587ff5d
test(linux): rename a table in the DDL conformance suite
datlechin Oct 13, 2026
572868a
merge linux-1b/grid
datlechin Oct 13, 2026
cbad327
merge linux-1b/completion
datlechin Oct 13, 2026
dd801e3
merge linux-1b/ddl
datlechin Oct 13, 2026
f3eafbc
merge linux-1b/workspace
datlechin Oct 13, 2026
7d7b06d
test(linux): build tab fixtures with the merged editor and structure …
datlechin Oct 13, 2026
43f280e
feat(linux-core): render one row as a qualified INSERT statement
datlechin Oct 13, 2026
379f137
fix(linux-grid): resolve cell events to row objects through the cell
datlechin Oct 13, 2026
b4536ad
fix(linux): read MySQL catalogue expressions and MariaDB EXTRA as SQL
datlechin Oct 13, 2026
52d6a3f
fix(linux): restate every MySQL and MariaDB column attribute on MODIFY
datlechin Oct 13, 2026
f5478fd
fix(linux): keep a column's collation on a PostgreSQL type change
datlechin Oct 13, 2026
1e4e786
fix(linux): restate collation and SPARSE in SQL Server ALTER COLUMN
datlechin Oct 13, 2026
3222ad6
test(linux): fake a write that answers once stopped and keep its cancel
datlechin Oct 13, 2026
8535cad
fix(linux): make saving, closing and logout event-driven
datlechin Oct 13, 2026
afb05f1
test(linux): cover closing every connection when the window closes
datlechin Oct 13, 2026
28a0ae7
fix(linux): keep a SQL Server column's data mask through ALTER COLUMN
datlechin Oct 13, 2026
dd8f4cc
docs(linux): record which column attributes each engine restates
datlechin Oct 13, 2026
6dba5e2
fix(linux): read a clicked close-prompt response before its close
datlechin Oct 13, 2026
f684f3d
fix(linux-grid): leave server-filled columns out of new rows
datlechin Oct 13, 2026
9a529d5
fix(linux-grid): hand grid events to the tab after the signal returns
datlechin Oct 13, 2026
53b075a
fix(linux-grid): keep a locked key cell's text selectable
datlechin Oct 13, 2026
0f14334
refactor(linux-grid): drop doc comments that restate the code
datlechin Oct 13, 2026
3f0e619
fix(linux): lock the structure draft while its save runs
datlechin Oct 13, 2026
208dd39
refactor(linux): give save rejections a type and keep services off ui
datlechin Oct 13, 2026
87e63e4
fix(linux): say why an edit waits on a save; ignore a second Save
datlechin Oct 13, 2026
5291d52
fix(linux): find MariaDB column checks by line and refuse a large SRID
datlechin Oct 13, 2026
844cb78
fix(linux): refuse a MySQL restatement naming a column it renames
datlechin Oct 13, 2026
a137ab3
fix(linux): refuse a SQL Server change SPARSE or a data mask rejects
datlechin Oct 13, 2026
e879a48
test(linux): compare every column attribute in the PG and MSSQL tests
datlechin Oct 13, 2026
6f602e2
docs(linux): record the restatement refusals and SQL Server 2016 floor
datlechin Oct 13, 2026
64cf98f
fix(linux): name each tab's own cause in the unknown save prompt
datlechin Oct 13, 2026
7a4b69f
test(linux): fail the stop test when the write never ran
datlechin Oct 13, 2026
76c359e
refactor(linux): move the window close state into tested CloseGuards
datlechin Oct 13, 2026
7249e3e
fix(linux): ask again before a switch replaces the tabs
datlechin Oct 13, 2026
83ba162
test(linux): pin the order a closing window closes its connections
datlechin Oct 13, 2026
4565be2
docs(linux): note the second switch prompt in the changelog
datlechin Oct 13, 2026
9a54926
merge linux-1b/ddl2
datlechin Oct 13, 2026
559c3ad
merge linux-1b/cells
datlechin Oct 13, 2026
5ed1909
merge linux-1b/close
datlechin Oct 13, 2026
1aa66a7
refactor(linux): drop an import the wave 5 merge left unused
datlechin Oct 13, 2026
8d1c750
fix(linux-grid): redraw tracked rows in place instead of swapping them
datlechin Oct 13, 2026
53d1cb3
feat(linux): order column renames and toggle PostgreSQL identity
datlechin Oct 13, 2026
1d9f994
feat(linux): hold a primary key column NOT NULL in the Structure tab
datlechin Oct 13, 2026
333967f
docs(linux): record rename order and PostgreSQL identity in ADR 0010
datlechin Oct 13, 2026
3f5a00a
refactor(linux-grid): hand grid events to the tab at once
datlechin Oct 13, 2026
1c803d7
fix(linux): drop and re-add SQL Server defaults around a type change
datlechin Oct 13, 2026
ab4a8b1
fix(linux): move the PostgreSQL identity's own sequence past its rows
datlechin Oct 13, 2026
e95d5b3
fix(linux): keep the Structure tab's key switches in step per engine
datlechin Oct 13, 2026
d252845
fix(linux): hold a case-only rename while another column has the name
datlechin Oct 13, 2026
42c3d03
build(linux): sort the app's dev-dependencies
datlechin Oct 13, 2026
b46af91
style(linux-grid): drop comments that restate the code
datlechin Oct 13, 2026
d045858
fix(linux-grid): cancel an open edit when a redraw changes its value
datlechin Oct 13, 2026
b9975a9
test(linux-grid): cover the refused edit redraw and unbind directly
datlechin Oct 13, 2026
39c484a
merge linux-1b/ddl3
datlechin Oct 13, 2026
f82a404
merge linux-1b/rows
datlechin Oct 13, 2026
996d907
test(linux): expect a SQL Server default to keep its constraint name
datlechin Oct 13, 2026
f7dcb4b
merge origin/linux (#3403 is wave 1, already in this branch)
datlechin Oct 13, 2026
c19191a
docs(linux): file the Format and file-reading entries under Fixed
datlechin Oct 13, 2026
c6913f9
fix(linux-grid): measure edits made during a save against what it wrote
datlechin Oct 13, 2026
1049487
fix(linux-grid): keep pending edits on their columns when columns change
datlechin Oct 13, 2026
9964422
fix(linux-grid): edit a cell from its whole value, not the display text
datlechin Oct 13, 2026
e42dcf3
fix(linux): count a default the parser refused as an unsaved change
datlechin Oct 13, 2026
4ed2cb9
fix(linux): declare a SQL Server identity column NOT NULL
datlechin Oct 13, 2026
fb5053f
ci(linux): scope a shellcheck exception to the GTK-thread I/O check
datlechin Oct 13, 2026
2c2ce19
test(linux): give the ClickHouse fixture three minutes to start
datlechin Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
77 changes: 77 additions & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ jobs:
echo "::error::crates/app/src/services must not hold thread_local! state"
exit 1
fi
- name: No blocking file I/O in the window and editor code
# This code runs on the GTK thread, where a slow disk or a
# network mount freezes the window. A storage call ending in
# _blocking is handed to spawn_blocking_task on the same line,
# which is what this check can see. Tests may do either.
run: |
# The $0 in the awk program is awk's record, not a shell variable.
# shellcheck disable=SC2016
found=$(find crates/app/src/ui/app crates/app/src/ui/editor -name '*.rs' ! -name tests.rs -print0 \
| xargs -0 awk '/^mod tests \{/ { nextfile } /std::fs::/ || (/_blocking\(/ && !/spawn_blocking_task\(/) { print FILENAME ":" FNR ": " $0 }')
if [ -n "$found" ]; then
echo "$found"
echo "::error::crates/app/src/ui/app and crates/app/src/ui/editor must not call std::fs, or a _blocking function outside spawn_blocking_task"
exit 1
fi
- name: Clippy
run: meson devenv -C _build cargo clippy --locked --workspace --all-targets -- -D warnings
- name: Clippy without default features
Expand Down Expand Up @@ -274,3 +289,65 @@ jobs:
with:
name: junit-tablepro-session
path: linux/target/nextest/ci/junit.xml

integration-app:
# The app links GTK 4.22 and libadwaita 1.9, which the host runner
# cannot build, so this runs in the fast job's image like the
# session job. Only the named test targets run: the app's startup
# test needs Meson and the fast job already covers it.
name: Docker tests (tablepro)
runs-on: ubuntu-24.04
needs: fast
container:
image: ubuntu:26.04
env:
DEBIAN_FRONTEND: noninteractive
options: -v /var/run/docker.sock:/var/run/docker.sock
defaults:
run:
working-directory: linux
steps:
- name: Install git
run: |
apt-get update
apt-get install -y --no-install-recommends ca-certificates git
git config --global --add safe.directory "$GITHUB_WORKSPACE"
working-directory: /
- uses: actions/checkout@v5
with:
ref: ${{ github.event_name == 'schedule' && 'linux' || github.ref }}
- name: Install system dependencies
# The -dev packages the app links, as in the fast job.
run: |
apt-get install -y --no-install-recommends \
build-essential \
curl \
pkgconf \
libgtk-4-dev \
libadwaita-1-dev \
libgtksourceview-5-dev \
libsqlite3-dev \
libkrb5-dev \
clang \
libclang-dev
- uses: dtolnay/rust-toolchain@ce678459e9fc7500d337468f904b95f1b5c10b5e # 1.98
with:
toolchain: "1.98"
- uses: Swatinem/rust-cache@v2
with:
workspaces: linux
key: tablepro-app
- uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12
with:
tool: cargo-nextest@0.9.144
- name: Verify docker is available
# No Docker CLI in the image; the API answers on the socket.
run: curl --fail --silent --show-error --unix-socket /var/run/docker.sock http://localhost/_ping
- name: Docker tests
run: cargo nextest run --locked --profile ci -p tablepro --test tracker_save_postgres --run-ignored only
- name: Upload JUnit report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: junit-tablepro
path: linux/target/nextest/ci/junit.xml
7 changes: 7 additions & 0 deletions linux/.config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ clickhouse = { max-threads = 1 }
ssh = { max-threads = 1 }
fixtures = { max-threads = 1 }
session = { max-threads = 1 }
app = { max-threads = 1 }

[profile.default]
# tiberius 0.13 stops reading at the first end of message, so an attention
Expand Down Expand Up @@ -48,6 +49,12 @@ test-group = 'fixtures'
filter = 'package(tablepro-session)'
test-group = 'session'

# The app's docker tests are integration targets. Its unit tests start
# no container, so they keep running in parallel.
[[profile.default.overrides]]
filter = 'package(tablepro) & kind(test)'
test-group = 'app'

[profile.ci]
fail-fast = false
# A hung test fails with its name after five minutes. Without a bound it
Expand Down
7 changes: 6 additions & 1 deletion linux/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ From the repository root, through the verification wrapper, which keeps the full

## CI

`.github/workflows/build-linux.yml` runs on a pull request that touches `linux/`: **Fast checks** (format, both clippy runs, build, every Meson suite, the Secret Service tests, `meson dist`), **Translations** (the POT template, with gettext 0.24) and **Docker tests**, one job per driver and SSH package.
`.github/workflows/build-linux.yml` runs on a pull request that touches `linux/`:

- **Fast checks**: format, both clippy runs, build, every Meson suite, the Secret Service tests, `meson dist`.
- **Translations**: the POT template, with gettext 0.24.
- **Docker tests** (`integration`): one matrix job per package on the host runner, for the PostgreSQL, MySQL, SQL Server and ClickHouse drivers, `tablepro-ssh` and `tablepro-test-fixtures`.
- **Docker tests (tablepro-session)** (`integration-session`) and **Docker tests (tablepro)** (`integration-app`): in the fast job's `ubuntu:26.04` image with the host's Docker socket, because the session crate links GLib 2.88 and the app GTK 4.22, which the host runner lacks. The app job runs only its named test targets, since the `startup` test needs Meson.
23 changes: 15 additions & 8 deletions linux/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Each engine's TLS stack takes trust in its own form, so the driver builds it:
|---|---|---|---|
| PostgreSQL | The system store (rustls-platform-verifier), plus the CA file | Yes | The rustls error |
| ClickHouse | The system store (rustls-platform-verifier), plus the CA file | Yes, as the login, with no password sent | The rustls error |
| MySQL, MariaDB | The Mozilla roots compiled into sqlx, plus the CA file. Not the system store | Yes | The rustls error |
| MySQL, MariaDB | The system store and the CA file as one PEM bundle (`tablepro_net::tls::mysql_ca_bundle_pem`), plus the Mozilla roots compiled into sqlx | Yes | The rustls error |
| SQL Server | The system store (loaded by tiberius), plus the CA file | No | rustls's message text, because tiberius keeps nothing else |
| SQLite | No TLS | | |

Expand Down Expand Up @@ -193,6 +193,8 @@ On Linux the system store is the distribution's CA bundle. Under Flatpak it is t

A window holds one `Session`. Every use of its connection goes through the `OperationRunner`, which adds no timer of its own: the deadline travels in `CallOptions` so the driver stops the statement on the server. The session's monitor probes every 30 s and calls the connection lost after three failed probes in a row, or as soon as `Connection::lost()` fires, which a driver resolves only when the link is gone, never for one dead backend in its pool. It then reconnects on a backoff from 5 s to 60 s. A failure that a retry cannot fix (authentication, TLS, configuration, a host key to review) parks the session until the user acts. A read that failed because its connection was lost runs once more on the replacement link; a save never does.

The session's `SchemaCatalog` holds what the window knows about the database: the schemas, each scope's objects and each table's columns. Callers that ask for the same thing at once share one fetch, a fetch runs as its own task so a view that closes does not stop it, and a failed refresh keeps the list it had. A reconnect commits the catalog the new connection read and marks the rest stale; `invalidate_table` does the same for one table after a schema change. The sidebar follows the default schema's list through a watch the window drops before the session closes. The editor's completion reads the catalog when it opens: the default schema's objects, a named schema's objects after `schema.`, and the columns of the tables the statement names.

## Workspace tab system

The active connection drives a single `AdwTabView` hosting heterogeneous tabs. The `App` component owns the strip; tabs are typed via the `WorkspaceTab` enum:
Expand All @@ -206,9 +208,11 @@ pub enum WorkspaceTab {
}
```

Each tab is a Relm4 `Controller<T>` whose widget the `AdwTabView` adopts. `App` keeps a `HashMap<Uuid, WorkspaceTab>` keyed by the tab's UUID; the canonical display order comes from `tab_view.pages()` (drag-reorderable). The UUID is stashed on the `AdwTabPage` via `glib::Quark` qdata so close / right-click actions can recover it.
Each tab is a gtk-rs widget with a composite template, which the `AdwTabView` adopts. `App` keeps a `HashMap<Uuid, WorkspaceTab>` keyed by the tab's UUID; the canonical display order comes from `tab_view.pages()` (drag-reorderable). The UUID is stashed on the `AdwTabPage` via `glib::Quark` qdata so close / right-click actions can recover it.

App routing is hub-and-spoke: a tab emits signals, and the closures App connects tag them with the tab's UUID and re-emit them as `AppMsg` inputs. App's `update` looks up the slot and calls back into the tab. This keeps the tabs ignorant of each other and gives App one place to enforce cross-tab invariants (reload the tabs on a table whose schema changed, close all tabs for a dropped table, etc.).

App routing is hub-and-spoke: every per-tab event becomes an output that App's `forward(...)` closure tags with the tab's UUID and re-emits as an `AppMsg::*ForTab(id, ...)`. App's `update` looks up the slot and dispatches back to the controller's input. This keeps the per-tab controllers ignorant of each other and gives App one place to enforce cross-tab invariants (refetch siblings after Save, close all tabs for a dropped table, etc.).
A browse or structure tab reads its own data through the session. A `RequestSequencer` keeps one request of each kind current: a new page stops the one it replaces, and a reply to a replaced request is dropped. A page and a count wait for the table's columns from the catalog. A read that failed is asked again once the session reconnects, while a tab whose reads all landed keeps its rows. Closing a tab stops its reads, so the server drops the statement.

Reopening a closed tab uses a 10-deep `VecDeque<ClosedTabDescriptor>` snapshot taken in `finish_close_workspace_tab` before the slot is dropped. The stack clears on disconnect because descriptors reference tables in the active connection.

Expand All @@ -219,26 +223,29 @@ Each window owns two registries holding the in-flight edit state for its tabs, k
| Registry | Handle | What it tracks | Materialised by |
|---|---|---|---|
| `ChangeTrackerRegistry` | `TabTrackerHandle` | Row-level INSERT / UPDATE / DELETE for Table tabs | `TabTrackerHandle::change_set` → `TrackedChanges` |
| `StructureTrackerRegistry` | `StructureTrackerHandle` | Column / index / FK / table-rename DDL for Structure tabs | `sql_ddl::materialize_ops` → `Vec<String>` |
| `StructureTrackerRegistry` | `StructureTrackerHandle` | The table's definition, the user's `TableDraft` and the `TableChanges` between them, for Structure tabs | `PendingStructure::plan` through the connection's `DdlDialect` → `DdlPlan` |

A handle is a newtype over `Rc<RefCell<_>>`. Every method borrows only for its own body, calls no GTK and no callbacks, and returns owned data (`CellOverlay`, `RowState`, `TrackerDelta`), so a grid bind that runs while a tab is applying a change never meets a held borrow. A row edit goes in as a `TrackerEdit` and comes back as a `TrackerDelta`: the rows to rebind, the draft rows the list store gains, loses or updates, and the new pending count. The tab applies it to its `gio::ListStore` after the call returns. `App::dirty_tabs` reads both registries for the close prompts, the teardown guard and the `•` title prefix. CI fails on a `thread_local!` under `crates/app/src/services`, since a process-wide registry would share one window's unsaved edits with every other window.
A handle is a newtype over `Rc<RefCell<_>>`. Every method borrows only for its own body, calls no GTK and no callbacks, and returns owned data (`CellOverlay`, `RowState`, `TrackerDelta`), so a grid bind that runs while a tab is applying a change never meets a held borrow. A row edit goes in as a `TrackerEdit` and comes back as a `TrackerDelta`: the rows to redraw, the draft rows the list store gains or loses, and the new pending count. The tab applies it to its `gio::ListStore` after the call returns. A grid row is a `RowObject` whose identity is fixed when it is made: a saved row's key and cells as fetched, or a draft's id, whose cells live only in the tracker. A tracked change bumps the row's `revision` property, and each cell bound to the row listens for it and draws again in place, so the store never swaps a row and its selection and focus stay put. The tab finds the row through a `GridRowIndex` of weak references keyed like the tracker. A grid cell resolves its row through its column view cell when an event fires, so an edit, a menu action or a toggle reaches the row it was made on even after rows above it came or went. Every pending change carries a `Touch` from the tracker's counter. An `UndoOp` holds the pending state on both sides of a step, touch included, so undo after A, B, C shows B, not the fetched value. `change_set` emits one change per row in touch order, and `plan_save` keeps that order: a row deleted and replaced by a draft with the same unique value saves, because its DELETE runs first. `App::dirty_tabs` reads both registries for the close prompts, the teardown guard and the `•` title prefix. CI fails on a `thread_local!` under `crates/app/src/services`, since a process-wide registry would share one window's unsaved edits with every other window.

The Structure tab is **snapshot + diff**, not per-op log. `original_*` snapshots capture the load-time schema; the diff against the live model produces ops via `sql_ddl::diff_to_ops`. Discard restores the snapshot. There is no per-op undo: Discard is the only restore point. The tracker just caches the most recent diff so out-of-band callers (close prompt, save dispatcher) read the same op list without re-deriving.
The Structure tab is **definition + draft**, not a per-change log. The `TableDefinition` one read returned is what the tab plans against; `diff_table` turns the draft into `TableChanges`, and the engine's `DdlDialect` plans them into statements in its own order. Discard copies the definition back over the draft, and is the only restore point. The tracker holds the latest `PendingStructure`, so the close prompt and the save dispatcher read the same change without the tab, and the save plans it again with the dialect of the connection that runs it ([ADR 0010](docs/decisions/0010-ddl-plans-and-table-definitions.md)).

## Persistence

| Data | Backend | Path / table |
|---|---|---|
| Saved connections | JSON, `g_file_set_contents_full` CONSISTENT + DURABLE, 0600 | `$XDG_CONFIG_HOME/tablepro/connections.json` |
| Connection passwords + SSH secrets | libsecret via `oo7` (Secret Service / KWallet) | keyring item per connection UUID |
| Per-connection workspace tabs | JSON, durable write, debounced 500 ms | `$XDG_STATE_HOME/tablepro/workspace_state.json` |
| Workspace tabs, one file per session | JSON, durable write, debounced 500 ms, off the GTK thread | `$XDG_STATE_HOME/tablepro/workspaces/<connection id>.json`, `file-<sha256 of the URI>.json` for an unsaved file |
| Editor tab text | one file per tab, debounced 500 ms | `$XDG_DATA_HOME/tablepro/drafts/<workspace name>/` |
| Query history | SQLite + FTS5 virtual table, 0600 | `$XDG_STATE_HOME/tablepro/history.db` |
| Application preferences | GSettings | schema `app.tablepro.TablePro` |
| Window size / maximized | GSettings | schema `app.tablepro.TablePro` |
| Per-table column widths | JSON, durable write | `$XDG_CONFIG_HOME/tablepro/column_widths.json` |
| Single-instance lock | `flock` on an empty file | `$XDG_RUNTIME_DIR/app/<app-id>/tablepro.lock` |

Forward compat: `WorkspaceTabRecord` uses `#[serde(other)] Unknown` so an old binary reading a newer file silently skips unknown variants instead of failing the whole load. `clamp_connection` runs on load to migrate legacy variants (`Browse`, `Structure { schema, table }`) into the unified `Table` shape.
`WorkspaceStore` reads a session's file once its session is adopted, and the window writes the tab list only after that read, so a write never lands on tabs not yet restored. A file that cannot be read, that another version wrote, or that is over 1 MiB is left as it is and nothing is written over it until the user picks Reset on the toast, which moves it aside as `<name>.json.corrupt-<seconds>`. Every open tab is saved and restored, with no cap on the count. Writes go through one task that keeps the newest record per file. Editor records carry a draft id, never the text, and the editor's `content-changed` signal carries no text either: the tab label reads at most 200 characters.

A restore deletes the drafts no saved or open tab names, except while a moved-aside copy of the session's file is there, since that copy may name them. A draft that cannot be read is moved aside as `<id>.sql.corrupt-<seconds>`, which the cleanup never deletes, and its tab opens empty with a toast. A disconnect writes the open tabs' text before their buffers go.

`SavedConnection::last_opened_at: Option<DateTime<Utc>>` is stamped on each successful connect via `touch_last_opened`. The welcome view sorts by recency-first with alphabetical tiebreaker; never-opened entries fall to the bottom.

Expand Down
Loading
Loading