Skip to content

feat(connections): colored tags and environment classification with production safety warning - #473

Merged
debba merged 7 commits into
TabularisDB:mainfrom
pokertour:feat/connection-environments-tags
Aug 4, 2026
Merged

feat(connections): colored tags and environment classification with production safety warning#473
debba merged 7 commits into
TabularisDB:mainfrom
pokertour:feat/connection-environments-tags

Conversation

@pokertour

Copy link
Copy Markdown
Contributor

Closes #472

What

Two complementary features for organizing connections and protecting production data, following the DBeaver model:

Free-form colored tags

  • ConnectionTag {id, name, color} stored in connections.json alongside groups; connections carry tag_ids.
  • Tag picker in the connection modal (appearance tab): inline creation with the shared accent palette, manage mode to rename/recolor/delete. Names are unique (case-insensitive) and capped at 32 chars; colors validated as hex.
  • Colored chips on connection cards and list rows; tag names participate in the connection search filter.
  • Tags ride along in export/import/backups: selective exports only include used tags, imports merge by id (import wins) then by name (unified onto the existing tag, tag_ids remapped), so re-creating "prod" on another machine never duplicates. Orphaned tag ids are tolerated.

Environment classification + production warning

  • Optional environment field per connection (development/staging/production), picked in the modal title bar, validated in the backend, preserved across duplicate/import.
  • Production identity: PROD badge on cards/rows, permanent red banner while the active connection is production, red ring on open sidebar entries.
  • Confirmation before any write on a production connection, with SQL preview and a per-connection "don't ask again" that lasts for the session. Detection is conservative: only SELECT/SHOW/DESCRIBE/PRAGMA/EXPLAIN-of-a-SELECT count as read-only — data-modifying CTEs, EXPLAIN ANALYZE <write> (which executes on Postgres) and unknown statement types (CALL, SET, …) all prompt. Guarded paths: editor runs (single + multi-statement), staged grid-edit commits, immediate cell edits, row insertion, notebook cells. Stacks with the existing dangerous-query guard.

Tests

  • Rust: unit tests for tag CRUD helpers (validation, dedup, name-merge on import, detach on delete).
  • Vitest: 7 new tests for isReadOnlyQuery; full suite green (154 files / 2841 tests).
  • i18n: all 9 locales updated.

Would love some tests and feedback, especially on database other than sqlite and mariadb.

Add ConnectionTag {id, name, color} stored in connections.json alongside
groups, with tag_ids on each connection. New connection_tags module with
list/create/update/delete_connection_tag and set_connection_tags commands:
names unique case-insensitively, hex colors validated, deleting a tag
detaches it everywhere. Tags ride along in export/import/backups and
survive update/duplicate.

UI: tag picker in the connection modal appearance tab (inline creation
with the shared accent palette, manage mode to rename/recolor/delete),
colored chips on connection cards and list rows, and tag names take part
in the connection search filter.

First slice of TabularisDB#472; environments and the production warning come next.
…p name length

set_connection_tags now drops unknown ids instead of failing, so a
connection imported with orphaned tag references can still be saved.
Imports unify tags that share a name (case-insensitive) onto the existing
tag and remap the imported connections tag_ids, so re-creating prod on
another machine no longer produces duplicates. Tag names are capped at 32
characters, enforced in the backend and mirrored by maxLength on inputs.
…warning

Add an optional environment field (development/staging/production) on each
connection, picked from the connection modal title bar and validated in the
backend; it survives duplicate and import.

Production connections get an unmistakable identity: a PROD badge on cards
and list rows, a permanent red banner while the active connection is
production, and a red ring on open sidebar entries.

Writes against production ask for confirmation first. Detection reuses the
sqlAnalysis infrastructure: only SELECT/SHOW/DESCRIBE/PRAGMA/EXPLAIN-of-a-
SELECT statements are considered read-only; data-modifying CTEs, EXPLAIN
ANALYZE of writes and unknown statement types (CALL, SET, ...) all prompt.
Guarded paths: editor single and multi-statement runs, staged grid-edit
commits, immediate cell edits, row insertion and notebook cells, stacking
with the existing dangerous-query guard. The dialog previews the SQL and
offers a per-connection snooze that lasts until the app restarts.

Completes TabularisDB#472 together with the tags feature.
Comment thread src-tauri/src/connection_tags.rs Outdated
let mut remap = std::collections::HashMap::new();
for tag in imported {
if let Some(same_id) = existing.iter_mut().find(|t| t.id == tag.id) {
*same_id = tag;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

CRITICAL: Import merge can create duplicate tag names

merge_imported_tags overwrites an existing tag by same-id (*same_id = tag) without checking whether the imported tag's new name collides with another existing tag. If existing tags are ["Prod", "Dev"] and an import renames "Prod" to "Dev", the result is two tags both named "Dev", breaking the unique-name invariant enforced by create_tag_impl/update_tag_impl. Consider validating the name collision before overwriting, or unifying onto the colliding tag instead.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src-tauri/src/commands.rs

// Merge connections and handle passwords
for mut new_conn in payload.connections {
for mut new_conn in payload_connections {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Imported connections bypass environment validation

apply_export_payload iterates over imported connections but never calls validate_environment on new_conn.environment. An exported payload (from an older version, a foreign app, or a manually crafted file) can contain arbitrary environment strings that bypass the validation enforced by save_connection and update_connection. Consider validating each imported connection's environment before saving.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src-tauri/src/commands.rs Outdated
detect_json_in_text_columns: original.detect_json_in_text_columns,
appearance: new_appearance,
tag_ids: original.tag_ids.clone(),
environment: original.environment.clone(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Duplicate propagates invalid environment without validation

duplicate_connection copies original.environment.clone() directly into the new connection. If the original holds an invalid environment value (e.g. introduced by file corruption or an unvalidated import), the invalid value propagates silently. Consider passing it through validate_environment.

Suggested change
environment: original.environment.clone(),
environment: validate_environment(original.environment.clone())?,

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src/pages/Editor.tsx
if (!targetTab) return;

if (!(await guardDangerousQuery(queries))) return;
if (!(await guardProductionWrite(activeConnectionId, queries.join(";\n")))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Missing guardProductionWrite in useCallback deps

guardProductionWrite is invoked inside runMultipleQueries but is absent from the dependency array. This risks a stale closure if the guard reference changes (e.g. when connections updates in useProductionGuard). The runQuery callback correctly includes it; this one should too.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread src/pages/Editor.tsx
return;

// Production safety: grid edits are writes, confirm before committing.
if (!(await guardProductionWrite(activeConnectionId))) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Missing guardProductionWrite in useCallback deps

guardProductionWrite is awaited inside handleSubmitChanges but is omitted from the useCallback dependency array. This means a stale guard reference can be captured, potentially using outdated connection state. Consider adding it to the deps list.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

setError(null);
try {
const tag = await createTag(newName.trim(), newColor);
onChange([...selectedIds, tag.id]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Stale closure can overwrite newer tag selections

handleCreate captures selectedIds from its closure at render time. If the user toggles other tag chips while createTag is in flight, the newer selections are overwritten when creation completes because the stale closure calls onChange([...selectedIds, tag.id]). Consider accepting a functional updater or reading the latest selected IDs via a ref.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

This is an incremental review since commit c1f466e ("address review feedback"). The branch subsequently merged main (commit 3f93477) and added a repair commit 30817f4 to resolve merge conflicts. All six issues from the prior review are re-verified as resolved at HEAD, and the repair commit was verified clean.

Previous findings re-verified

All six previously raised findings are resolved at current HEAD and remain unchanged through the merge:

File Line Finding Status
src-tauri/src/connection_tags.rs 157 Duplicate tag names on import merge (same_name-first unification added) Resolved
src-tauri/src/commands.rs 6017 Imported connections bypass environment validation (now validated per connection) Resolved
src-tauri/src/commands.rs 1403 Duplicate propagates invalid environment (now passed through validate_environment) Resolved
src/pages/Editor.tsx 1341 Missing guardProductionWrite in runMultipleQueries deps (now included) Resolved
src/pages/Editor.tsx 2774 Missing guardProductionWrite in handleSubmitChanges deps (now included) Resolved
src/components/modals/NewConnectionModal/TagSelector.tsx 86 Stale closure overwrites tag selections (now reads selectedIdsRef.current) Resolved

Repair commit (30817f4) verification

  • src-tauri/src/commands.rs: payload.connectionspayload_connections rename is consistent with the binding declared at line 5995; borrow ordering (&mut pass at 6016, move at 6021) is valid.
  • src-tauri/src/pool_manager.rs: removed let options = build_mysql_options(...) is safe — the binding is no longer referenced in get_mysql_pool_for_database_with_id, which delegates to build_and_connect_mysql_pool (which builds options internally at line 336).
  • src-tauri/src/sqlite_database.rs: save_connection(app, name, params, None, None) matches the 5-param signature (app, name, params, detect_json, environment).
  • src/components/layout/MainLayout.tsx: ProductionBanner import (line 8) and render (line 31) correctly restored.
  • src/components/modals/NewConnectionModal.tsx: added save import is used at line 1505 — no unused import.
  • tests/pages/Connections.test.tsx: useConnectionTags mock is consistent.

No new issues were introduced by the merge or repair. Production-write guarding remains in place and balanced across write paths (Editor.tsx, DataGrid.tsx line 993/1033, NotebookView.tsx line 377/437, NewRowModal.tsx line 182).

Files Reviewed (10 files)
  • src-tauri/src/commands.rs
  • src-tauri/src/connection_tags.rs
  • src-tauri/src/pool_manager.rs
  • src-tauri/src/sqlite_database.rs
  • src-tauri/Cargo.lock
  • src/components/layout/MainLayout.tsx
  • src/components/modals/NewConnectionModal.tsx
  • src/components/modals/NewConnectionModal/TagSelector.tsx
  • src/pages/Editor.tsx
  • tests/pages/Connections.test.tsx
Previous Review Summaries (2 snapshots, latest commit c1f466e)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit c1f466e)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (12 files)
  • src-tauri/src/commands.rs
  • src-tauri/src/connection_tags.rs
  • src/components/modals/NewConnectionModal/AppearanceSection.tsx
  • src/components/modals/NewConnectionModal/TagSelector.tsx
  • src/components/modals/NewConnectionModal/palette.ts
  • src/components/modals/NewRowModal.tsx
  • src/components/notebook/NotebookView.tsx
  • src/components/ui/DataGrid.tsx
  • src/contexts/ProductionGuardContext.tsx
  • src/hooks/useConnectionTags.ts
  • src/hooks/useProductionGuard.ts
  • src/pages/Editor.tsx

Previous review (commit e0118e0)

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 5
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src-tauri/src/connection_tags.rs 150 Import merge can create duplicate tag names

WARNING

File Line Issue
src-tauri/src/commands.rs 5059 Imported connections bypass environment validation
src-tauri/src/commands.rs 1076 Duplicate propagates invalid environment without validation
src/pages/Editor.tsx 885 Missing guardProductionWrite in useCallback deps
src/pages/Editor.tsx 2240 Missing guardProductionWrite in useCallback deps
src/components/modals/NewConnectionModal/TagSelector.tsx 78 Stale closure can overwrite newer tag selections
Additional Finding (summary-only)
File Issue
src-tauri/src/commands.rs apply_export_payload overwrites existing connections with *existing = new_conn. Older export backups deserialize missing tag_ids and environment to None via serde(default), so importing an older backup wipes any existing tags/environment on target connections. Consider merging fields instead of full overwrite for backward compatibility.
Files Reviewed (38 files)
  • src-tauri/src/commands.rs - 2 issues
  • src-tauri/src/connection_tags.rs - 1 issue
  • src/pages/Editor.tsx - 2 issues
  • src/components/modals/NewConnectionModal/TagSelector.tsx - 1 issue
  • src-tauri/src/models.rs
  • src-tauri/src/persistence.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/connection_cache_tests.rs
  • src-tauri/src/connection_import/analyzer.rs
  • src-tauri/src/connection_import/convert.rs
  • src-tauri/src/connection_import/tabularis.rs
  • src-tauri/src/export_import_tests.rs
  • src/components/connections/ConnectionCard.tsx
  • src/components/connections/ConnectionListItem.tsx
  • src/components/connections/EnvironmentBadge.tsx
  • src/components/connections/TagChips.tsx
  • src/components/layout/MainLayout.tsx
  • src/components/layout/ProductionBanner.tsx
  • src/components/layout/sidebar/OpenConnectionItem.tsx
  • src/components/modals/NewConnectionModal.tsx
  • src/components/modals/NewConnectionModal/AppearanceSection.tsx
  • src/components/modals/NewRowModal.tsx
  • src/components/notebook/NotebookView.tsx
  • src/components/ui/DataGrid.tsx
  • src/contexts/DatabaseContext.ts
  • src/contexts/ProductionGuardContext.tsx
  • src/hooks/useConnectionTags.ts
  • src/main.tsx
  • src/pages/Connections.tsx
  • src/types/tags.ts
  • src/utils/environment.ts
  • src/utils/sqlAnalysis.ts
  • src/utils/sqlAnalysis.test.ts
  • src/i18n/locales/de.json
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/it.json
  • src/i18n/locales/ja.json
  • src/i18n/locales/ru.json
  • src/i18n/locales/tl.json
  • src/i18n/locales/zh.json

Fix these issues in Kilo Cloud


Reviewed by laguna-s-2.1:free · Input: 456K · Output: 77.4K · Cached: 2.1M

- unify tag rename collisions on import instead of duplicating names,
  remapping existing connections too
- normalize environment on import and duplicate (invalid -> unclassified)
- add guardProductionWrite to useCallback deps in Editor
- read latest tag selection via ref in async TagSelector handlers
- move PALETTE and useProductionGuard to dedicated files (react-refresh)
- load tags via promise chain instead of setState in effect body
@debba

debba commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

@pokertour can you resolve merge conflicts?

@debba

debba commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Thanks @pokertour for this contribution! This looks like a great starting point that we can expand on in future PRs. I pushed a small commit on top to repair the merge with main (two build errors, a few test mocks, and the ProductionBanner render that got lost in the conflict resolution).

Some ideas for follow-up PRs building on this foundation:

  • Custom environment colors: let users pick the color for each environment tier, or even define custom tiers beyond development/staging/production.
  • Per-environment action policies: configure which operations are allowed, blocked, or require confirmation based on the environment (e.g. block DROP/TRUNCATE in production, require confirmation for UPDATE/DELETE without a WHERE clause, allow everything in development).
  • Read-only mode: an option to open production connections in read-only mode by default.
  • Environment visibility in more places: connection tab colors, status bar badge, window title, and in the query confirmation dialogs.
  • Tag-based filtering and grouping: filter the connections list by tag and optionally group by environment.

Merging, thanks again!

@debba
debba merged commit cc4c08a into TabularisDB:main Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Connection environments (dev/prod) with production safety warning, plus free-form colored tags

2 participants