DuplicateIds repro — PRE-FIX (master @ 4e232c9)
================================================

Test driver (tests/test_app_integration.py::test_*_remount_does_not_raise_duplicate_ids)
sequence equivalent to the user's reported UI path:

  1. Boot the TUI with a fake lake holding two tables (table_id 1, 2).
  2. Post TableSelected(table_id=1, schema=main, name=events)
       → enters catalog-mode, mounts TableDetailsView with table 1.
  3. Set TabbedContent active = "snapshots"
       → on_tabbed_content_tab_activated mounts SnapshotTimelineView with table 1.
  4. Post TableSelected(table_id=2, schema=main, name=orders)
       → on_table_selected sets tabs.active = "details" AND directly calls
         _ensure_catalog_view("details"). Both paths invoke
         _refresh_catalog_view → view.load(table_id=2), so load() runs twice
         in rapid succession on the cached TableDetailsView instance.

Probe output (tests/__pycache__-side python script):

    BEFORE first TableSelected. _exception: None
    AFTER first TableSelected. _exception: None
    AFTER switch to snapshots. _exception: None
    pause TS2 1 error: WaitForScreenTimeout Timed out while waiting for widgets to process pending messages.
    AFTER second TableSelected. _exception: None

TEXTUAL_LOG=/tmp/textual-prefix.log captured the real failure inside the
worker (app._exception is not surfaced for handler-side errors):

    <Worker ERROR name='work' group='table-details-load' ...> failed
      DuplicateIds("Tried to insert a widget with ID 'columns-panel', but a widget
      already exists with that ID (Vertical(id='columns-panel', classes='panel'));
      ensure all child widgets have a unique ID.")
    StateChanged(... <WorkerState.ERROR: 4>) >>> TableDetailsView(id='view-table-details')

Same shape on snapshots tab (id='timeline-header'), files tab (id='files-header'),
etc. — every view whose load() flow calls self.remove_children() followed by
synchronous self.mount(Static(id='...')) is affected, because remove_children()
defers via Prune messages while mount() appends to the NodeList synchronously.

Root cause: app.py:715 on_table_selected paths invoke load() twice for the
"details" tab when navigating from another tab (one call from the direct
_ensure_catalog_view, one from the watcher that fires when tabs.active is set).
The second call hits a view whose timeline-* / details-* children are still in
the NodeList because the prior remove_children() prune has not been processed
yet — DuplicateIds.

Pytest verification (pre-fix):

    $ uv run pytest tests/test_app_integration.py -k remount -x --tb=short
    tests/test_app_integration.py F
    E   textual.pilot.WaitForScreenTimeout: Timed out while waiting for widgets
        to process pending messages.
