Skip to content

feat(ios): add Shortcuts actions to insert table rows#1793

Open
datlechin wants to merge 4 commits into
mainfrom
feat/ios-shortcuts-add-row
Open

feat(ios): add Shortcuts actions to insert table rows#1793
datlechin wants to merge 4 commits into
mainfrom
feat/ios-shortcuts-add-row

Conversation

@datlechin

Copy link
Copy Markdown
Member

Adds iOS Shortcuts support so users can append data to a table from the Shortcuts app, the Share Sheet, or by voice, without opening the app. Requested in discussion #1788.

What's new

Two background App Intents in TableProMobile:

  • Add Row to Table - insert one row from a JSON object or a CSV row.
  • Add Rows to Table - insert many rows from a JSON array, CSV text, or a file.

Both use cascading pickers: Connection -> optional Database/Schema -> Table. The data is matched to the table's columns by name; columns you omit fall back to the database default (auto-increment keys, for example). Each action returns the inserted row count and speaks a short confirmation.

How it works

The intents reuse the app's existing headless path (ConnectionManager -> IOSDriverFactory -> driver), so they run in the background. Connection passwords come from the Keychain, and the action requires the device to be unlocked (authenticationPolicy = .requiresAuthentication).

Relational databases only (MySQL, MariaDB, PostgreSQL, Redshift, SQL Server, SQLite, DuckDB). Non-relational connections are refused with a clear error, as are read-only connections.

Security

Values flow through the same escaped-insert path the in-app Insert form uses. Identifiers are bounded: the table comes from the picker and column names are validated against the live schema, so unknown keys are rejected before any SQL is built. Capped at 10,000 rows per run.

Tests

  • RowPayloadTests - JSON object/array, CSV (quoted fields), null/bool handling, malformed and single-row errors.
  • RowInsertPlannerTests - per-dialect quoting, empty-PK skip, NULL, unknown-column and no-column errors, quote escaping.
  • IntentConnectionLoaderTests - decodes the full connection model.

swiftlint --strict clean. Docs: new external-api/ios-shortcuts page. CHANGELOG updated.

Closes #1788

@mintlify

mintlify Bot commented Jun 30, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jun 30, 2026, 11:56 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c4c8d2d9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

rows: [PayloadRow]
) async throws -> Int {
let columns = try await driver.fetchColumns(table: table, schema: schema)
let statements = try RowInsertPlanner.statements(table: table, type: type, columns: columns, rows: rows)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Qualify shortcut inserts with the selected schema

When a Shortcut selects a non-default schema on PostgreSQL/MSSQL/DuckDB, insert fetches columns using the resolved schema but then builds statements from only the bare table name. The resulting INSERT INTO "table" resolves against the connection's current/default schema, so it can write to public/dbo/main instead of the schema chosen in the picker, or fail if only the selected schema has that table. Pass the schema through to the insert SQL (or switch schema before executing) so the table picker target is the table that receives the rows.

Useful? React with 👍 / 👎.

guard let connection = IntentConnectionLoader.connection(id: connectionId) else {
throw IntentDataError.connectionNotFound
}
if connection.safeModeLevel.writePermission == .blocked {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor confirm-writes before shortcut inserts

For connections set to Confirm Writes, writePermission is .requiresConfirmation, but this guard only blocks .blocked, so the Shortcut proceeds to write in the background after device unlock. Other mobile write paths such as QueryEditorView.executeQuery and InsertRowView.insertRow stop to confirm this state; without the same handling here, users relying on Confirm Writes can trigger an automation or voice shortcut that inserts rows without the required confirmation.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ef26e7e67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

insertColumns.append(column.name)
insertValues.append(value.sqlValue)
}
guard !insertColumns.isEmpty else { return nil }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject uninsertable rows instead of skipping them

When an Add Rows payload contains a mix of valid rows and a row with no insertable values (for example {} or only an empty auto-generated primary key), returning nil inside this compactMap silently drops that input row while the remaining rows are inserted. The all-empty case errors later, but mixed batches report a smaller inserted count instead of failing the batch or identifying the bad row, so an automation can lose records without a clear error.

Useful? React with 👍 / 👎.

Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d74493066

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +10 to +15
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { throw IntentDataError.emptyPayload }

let rows = trimmed.hasPrefix("{") || trimmed.hasPrefix("[")
? try parseJSON(trimmed)
: try parseCSV(raw)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip BOMs before detecting the payload format

When the shortcut input comes from a UTF-8-with-BOM file or text, trimmingCharacters(in: .whitespacesAndNewlines) leaves the leading U+FEFF in place. In that case a JSON file no longer satisfies hasPrefix("{")/hasPrefix("[") and is parsed as CSV, while a CSV file keeps the BOM on the first header name (for example title), so column matching later reports an unknown column even though the file is otherwise valid. Strip a leading BOM before format detection/header parsing so common exported CSV/JSON files work.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant