Skip to content
Merged
44 changes: 44 additions & 0 deletions .agents/skills/service-repository-reference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ cross-layer data interchange.
| `create_new_request(collection_id, method, url, name, ...)` | `RequestModel` | Create a request |
| `rename_request(request_id, new_name)` | `None` | Update name |
| `delete_request(request_id)` | `None` | Delete a single request |
| `duplicate_request(request_id)` | `RequestModel` | Clone request + saved responses + assertions; unique `` Copy`` name |
| `unique_duplicate_request_name(base_name, existing_names)` | `str` | ``{base} Copy`` or ``{base} Copy N`` |
| `update_request_collection(request_id, new_collection_id)` | `None` | Move request |
| `update_collection_parent(collection_id, new_parent_id)` | `None` | Move collection |
| `save_response(request_id, ...)` | `int` | Persist a response snapshot, return its ID |
Expand Down Expand Up @@ -79,6 +81,25 @@ cross-layer data interchange.
| `delete_run(run_id)` | `bool` | Delete a single run (True if found) |
| `delete_runs_for_collection(collection_id)` | `int` | Delete all runs for a collection, return count |

### Request history repository (`request_history_repository.py`)

Metadata in SQLite; bodies/snapshots via `body_store.py` under
`user_history_root()` (`postmark_user_data_dir()/history`).

| Function | Returns | Purpose |
|----------|---------|---------|
| `insert_entry(...)` | `dict[str, Any]` | Insert row + write body/snapshot files; truncate body to `max_response_bytes` |
| `get_entry(entry_id)` | `dict \| None` | Row + loaded body/snapshot bytes |
| `list_entries_for_sidebar(search?, limit?)` | `list[dict]` | Newest-first global list (left rail); search always capped by `limit` |
| `list_for_request(request_id, search?, limit?)` | `list[dict]` | Newest-first rows for one persisted `request_id`; search capped |
| `delete_entry(entry_id)` | `bool` | Delete one row and on-disk payload files |
| `prune_old_entries(retention_days, max_items_per_day, unlimited_per_day)` | `None` | Drop rows older than retention and over per-day cap |
| `nullify_request_id(request_id)` | `None` | Set `request_id` NULL when collection request deleted |
| `local_date(executed_at)` | `date` | Local calendar date for per-day caps |

`body_store`: `write_body`, `read_body`, `write_request_snapshot`,
`read_request_snapshot`, `delete_entry_files`, `reconcile_orphans`.

### Local script repository (`local_script_repository.py`)

| Function | Returns | Purpose |
Expand Down Expand Up @@ -119,6 +140,7 @@ directly to the repository with no added logic.
| `create_request(collection_id, method, url, name, ...)` | `name.strip()`, `method.upper()`, rejects empty |
| `rename_request(id, new_name)` | `new_name.strip()`, rejects empty |
| `delete_request(id)` | Logging only |
| `duplicate_request(id)` | Logging only; returns new `RequestModel` |
| `move_request(id, new_collection_id)` | Passthrough |
| `update_collection(id, **fields)` | Passthrough (generic field update) |
| `update_request(id, **fields)` | Passthrough (generic field update) |
Expand Down Expand Up @@ -212,6 +234,28 @@ history CRUD.
| `delete_run(run_id)` | Delete a single run |
| `delete_runs(collection_id)` | Delete all runs for a collection |

### RequestHistoryService (`services/request_history_service.py`)

Module-level functions; class re-exports them as `@staticmethod` aliases.

| Method | Purpose |
|--------|---------|
| `gather_send_identity(ctx, editor, data)` | Capture method/url/name at send start |
| `record_send(identity, response, original_request, settings)` | Persist send; prune per settings; return entry id |
| `list_for_sidebar(search?)` | List all entries (left-rail global History) |
| `entry_to_http_response_dict(entry)` | Map stored entry → `ResponseViewer.load_stored_response` dict |
| `list_for_request(request_id, search?)` | List sends for one saved request (right rail) |
| `get_entry(entry_id)` | Full row with file payloads |
| `entry_to_detail_snapshot(entry)` | Shape for HistoryPanel read-only detail tabs |
| `build_replay_request_dict(entry)` | Editor load dict from snapshot |
| `build_send_payload_from_entry(entry)` | HTTP replay worker payload |
| `delete_entry(entry_id)` | Remove row and payload files |

TypedDicts: `SendIdentityDict`, `RequestHistoryEntryDict` (includes `was_persisted_request` for `(deleted)` / `(draft)` labels).

Settings: `HistorySettingsManager` (`history/retention_days`, `max_items_per_day`,
`unlimited_per_day`, `save_responses`, `max_response_bytes`).

### LocalScriptService (`services/local_script_service.py`)

All methods are `@staticmethod`. UI must use this module, not `database/`.
Expand Down
51 changes: 42 additions & 9 deletions .agents/skills/signal-flow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,47 @@ ResponseViewerWidget.save_availability_changed(bool)
→ _TabControllerMixin lambda
→ MainWindow._refresh_sidebar()
→ RightSidebar.set_saved_response_context(...can_save_current=...)

MainWindow._refresh_sidebar (request tab)
→ RightSidebar.set_request_history_context(...)
→ HistoryPanel.set_request_context(...) + refresh()

on_send_finished → _record_request_history
→ RequestHistoryService.record_send(...)
→ _request_history_panel.refresh() when recorded request_id matches active tab
→ _global_history_panel.refresh() always

HistoryPanel.entry_open_requested(int entry_id) [global instance only]
→ MainWindow._open_from_global_history
→ existing request: _open_request + right History schedule_detail_load (async detail)
+ RightSidebar.open_panel("request_history") + focus_entry (deferred)
→ orphan/deleted: _open_draft_request + load_request(snapshot) + load_stored_response
(draft sidebar: History/Saved Responses disabled)

HistoryPanel.replay_requested(int entry_id)
→ MainWindow._replay_request_history_entry
→ RequestHistoryService.build_send_payload_from_entry
→ _launch_http_send (auth_data=None; no pre/post scripts)
→ ResponseViewer only; new history row on finish
→ ResponseViewer ``responseReplayIndicator`` (source row link)

ResponseViewer.replay_history_link_clicked(int entry_id)
→ MainWindow._on_replay_history_link_clicked
→ RightSidebar.open_panel("request_history")
→ HistoryPanel.focus_entry(entry_id)

HistoryPanel.delete_requested(int entry_id)
→ MainWindow._delete_request_history_entry
→ RequestHistoryService.delete_entry
→ HistoryPanel.refresh()

CollectionWidget.load_finished
→ MainWindow._on_load_finished (main stack + menu/status)
→ session_restore.begin_session_restore (batched tab restore)
→ MainWindow.session_restore_finished

MainWindow (startup)
→ LocalProjectConfigWorker (QThread): ensure_local_project_config / sync_all
```

### Folder editor flow
Expand All @@ -276,13 +317,6 @@ FolderEditor.collection_changed(dict)
→ MainWindow handler → CollectionService.update_collection(...)
```

### History panel flow

```
HistoryPanel.entry_clicked(method, url)
→ (wired to open or populate editor)
```

### Toggle actions flow

```
Expand All @@ -293,7 +327,7 @@ MainWindow._toggle_sidebar_action.triggered
→ _toggle_sidebar (collapse/expand left flyout; rail stays visible; stacked page unchanged)

MainWindow._toggle_bottom_action.triggered
→ _toggle_bottom_panel (show/hide console/history)
→ _toggle_bottom_panel (show/hide console)

MainWindow._toggle_layout_action.triggered
→ _toggle_layout_orientation (horizontal ↔ vertical)
Expand Down Expand Up @@ -579,7 +613,6 @@ All other signals in the flow diagrams above are fully wired.
| `CodeEditorWidget` | `validation_changed` | `Signal(list)` |
| `CodeEditorWidget` | `run_single_test_requested` | `Signal(str)` — per-`pm.test` gutter Run |
| `CodeEditorWidget` | `debug_single_test_requested` | `Signal(str)` — per-`pm.test` gutter Debug |
| `HistoryPanel` | `entry_clicked` | `Signal(str, str)` |

## MainWindow signal wiring summary

Expand Down
1 change: 0 additions & 1 deletion .claude/scheduled_tasks.lock

This file was deleted.

60 changes: 44 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ Fastest paths to understand and navigate the codebase:

- **All services at a glance:** Read `src/services/__init__.py` — re-exports
`CollectionService`, `EnvironmentService`, `ImportService`,
`RunHistoryService`, and key TypedDicts (`RequestLoadDict`,
`VariableDetail`, `LocalOverride`).
`RunHistoryService`, `RequestHistoryService`, and key TypedDicts
(`RequestLoadDict`, `VariableDetail`, `LocalOverride`,
`RequestHistoryEntryDict`, `SendIdentityDict`).
- **HTTP subsystem:** Read `src/services/http/__init__.py` — re-exports
`HttpService`, `GraphQLSchemaService`, `SnippetGenerator`,
`SnippetOptions`, `HttpResponseDict`, `parse_header_dict`.
Expand All @@ -116,8 +117,8 @@ Fastest paths to understand and navigate the codebase:
- **All DB models:** Read `src/database/database.py` — re-exports collection,
environment, run-history, and local-script ORM models (`CollectionModel`,
`RequestModel`, `SavedResponseModel`, `EnvironmentModel`, `RunHistoryModel`,
`RunResultModel`, `LocalScriptFolderModel`, `LocalScriptModel`,
`SnippetModel`).
`RunResultModel`, `RequestHistoryEntryModel`, `LocalScriptFolderModel`,
`LocalScriptModel`, `SnippetModel`).
- **Collection CRUD vs queries:** Mutations live in
`collection_repository.py`; read-only tree/breadcrumb/ancestor queries
live in `collection_query_repository.py`.
Expand Down Expand Up @@ -148,7 +149,8 @@ src/
├── main.py # Entry point — configure_before_qapplication + QApplication + init_db()
├── qt_app_init.py # Hi-DPI bootstrap (before first QApplication; tests + app)
├── database/ # Engine, models, repository
│ ├── database.py # init_db(), get_session(), migration
│ ├── data_paths.py # project_root(), postmark_user_data_dir(), user_history_root()
│ ├── database.py # init_db(), get_session(), migration; reconcile_orphans on startup
│ └── models/
│ ├── base.py # DeclarativeBase
│ ├── collections/
Expand Down Expand Up @@ -187,6 +189,11 @@ src/
│ ├── request_assertion_repository.py # CRUD for declarative assertion rows
│ └── model/
│ └── request_assertion_model.py # RequestAssertionModel (subject/operator/expected)
│ └── request_history/
│ ├── body_store.py # Atomic body/snapshot files under user_history_root()
│ ├── request_history_repository.py # insert, get, list, prune, nullify_request_id
│ └── model/
│ └── request_history_entry_model.py # RequestHistoryEntryModel (metadata in SQLite)
├── services/ # Service layer (UI ↔ DB bridge)
│ ├── collection_service.py # CollectionService (static methods)
│ ├── assertion_service.py # AssertionService + AssertionDict — declarative tests CRUD + compile
Expand All @@ -195,6 +202,7 @@ src/
│ ├── environment_service.py # EnvironmentService (variable substitution + TypedDicts)
│ ├── import_service.py # ImportService (parse + persist)
│ ├── run_history_service.py # RunHistoryService (run history CRUD bridge)
│ ├── request_history_service.py # RequestHistoryService — gather_send_identity, record_send, get/list
│ ├── script_service.py # ScriptService (script chain resolution)
│ ├── scripting/ # Script execution sub-package
│ │ ├── local_path_policy.py # Re-export path_policy (UI/service)
Expand Down Expand Up @@ -280,11 +288,14 @@ src/
├── main_window/ # Top-level MainWindow sub-package
│ ├── window.py # MainWindow widget + signal wiring
│ ├── send_pipeline.py # _SendPipelineMixin — HTTP send (re-exports debug-hover helpers)
│ ├── history_navigation/ # _HistoryNavigationMixin — open global history into tabs + right History
│ ├── send_pipeline_debug.py # _merge_debug_hover_values, _debug_hover_root_objects, …
│ ├── send_pipeline_postresponse.py # on_send_finished, run_post_response_script_with_live_response
│ ├── send_pipeline_debug_session.py # on_debug_paused/step/finished, end_debug_ui
│ ├── draft_controller.py # _DraftControllerMixin — draft tab open/save
│ ├── tab_controller.py # _TabControllerMixin — tab open/close/switch
│ ├── session_restore.py # Batched session tab restore after load_finished
│ ├── startup_workers.py # LocalProjectConfigWorker — mirror sync off GUI thread
│ ├── tab_nav/ # Tab activation back/forward stacks
│ │ ├── history.py # _TabNavHistoryMixin — Go menu Ctrl+Alt+arrows
│ │ └── __init__.py
Expand All @@ -310,16 +321,24 @@ src/
│ ├── debug_panel.py # DebugPanel facade — DebugControls + DebugInspectorSplit
│ ├── debug_call_stack_panel.py # CallStackPanel — frame list + frame_selected
│ ├── debug_watch_in_tree.py # Watches section rows + format_watch_display / rebuild_watch_rows
│ └── saved_responses/ # Saved responses sub-package
│ ├── panel.py # SavedResponsesPanel — saved example list/detail flyout
│ ├── search_filter.py # _PanelSearchFilterMixin — body search/filter
│ ├── helpers.py # Formatting helpers (body size, language detect, etc.)
│ └── delegate.py # Custom delegate for saved response list items
│ ├── saved_responses/ # Saved responses sub-package
│ │ ├── panel.py # SavedResponsesPanel — saved example list/detail flyout
│ │ ├── search_filter.py # _PanelSearchFilterMixin — body search/filter
│ │ ├── helpers.py # Formatting helpers (body size, language detect, etc.)
│ │ └── delegate.py # Custom delegate for saved response list items
│ └── history/ # Send-history flyouts (right per-request + left global); date_filter/ popup + mixin
│ ├── panel.py # HistoryPanel — list/detail + requestHistorySearch
│ ├── global_mode/ # Global mode mixin + Enter key filter (left rail)
│ ├── panel_detail_tabs.py # Read-only Headers / Request Headers / Request Body tabs
│ ├── delegate.py # HistoryEntryDelegate — status badge + date group headers
│ ├── helpers.py # Date grouping, list populate, row meta, sent headers
│ └── search_filter.py # Re-exports body search mixin
├── styling/ # Visual theming and icons
│ ├── theme.py # Palettes, colours, status bar / left-rail chrome, badge/tree geometry, left-nav panel margins, method_color(), status_color()
│ ├── language_icons.py # Brand SVG pixmaps for JS / TS / Python tiles
│ ├── theme_manager.py # ThemeManager — QPalette + QSettings
│ ├── tab_settings_manager.py # TabSettingsManager — request-tab QSettings bridge (preview, limits, activate-on-close, wrap mode)
│ ├── history_settings_manager.py # HistorySettingsManager — QSettings history/* send retention
│ ├── global_qss.py # build_global_qss() — global stylesheet builder
│ └── icons.py # Phosphor font-glyph icon provider (phi())
├── widgets/ # Reusable shared components
Expand Down Expand Up @@ -390,6 +409,8 @@ src/
│ ├── tree_overlay_rename.py # _TreeOverlayRenameMixin — overlay rename + click-away
│ └── collection_tree_delegate.py # Custom delegate for method badges
├── dialogs/ # Modal dialogs
│ ├── settings/
│ │ └── history_page.py # Settings → History page (retention, bodies, storage path)
│ ├── collection_runner/
│ │ ├── __init__.py # Re-exports RunnerConfigView, RunnerResultsView, RunnerWorker
│ │ ├── config.py # RunnerConfigView (env selector, request checklist, data file, iterations, delay)
Expand All @@ -403,8 +424,7 @@ src/
│ ├── environment_selector.py
│ └── environment_sidebar_panel.py
├── panels/ # Bottom / side panels
│ ├── console_panel.py
│ └── history_panel.py
│ └── console_panel.py
└── request/ # Request/response editing
├── folder_editor/ # Folder/collection detail editor sub-package
│ ├── editor_widget.py # FolderEditorWidget — main editor class
Expand Down Expand Up @@ -454,6 +474,7 @@ src/
│ └── toolbar.py # _DiffToolbar — search, nav, whitespace, copy
├── response_viewer/ # ResponseViewer sub-package
│ ├── viewer_widget.py # ResponseViewer — response display widget
│ ├── replay_indicator.py # ResponseReplayIndicator — replayed-send status corner pill
│ ├── search_filter.py # _SearchFilterMixin — response search/filter
│ ├── test_results_mixin.py # _TestResultsMixin — test results tab
│ └── pre_request_mixin.py # _PreRequestMixin — pre-request script output tab
Expand Down Expand Up @@ -482,7 +503,10 @@ tests/
│ │ ├── test_request_assertion_repository.py
│ │ ├── test_script_version_local_script.py
│ │ ├── test_environment_repository.py
│ │ └── test_run_history_repository.py
│ │ ├── test_run_history_repository.py
│ │ ├── test_data_paths.py
│ │ ├── test_request_history_body_store.py
│ │ └── test_request_history_repository.py
│ └── services/ # Service layer tests
│ ├── test_service.py
│ ├── test_environment_service.py
Expand All @@ -504,6 +528,7 @@ tests/
│ ├── test_assertions_compiler.py
│ ├── test_deno_manager.py
│ ├── test_runtime_settings.py
│ ├── test_request_history_service.py
│ └── http/ # HTTP service tests
│ ├── test_http_service.py
│ ├── test_graphql_schema_service.py
Expand All @@ -527,10 +552,14 @@ tests/
├── sidebar/ # Sidebar widget tests
│ ├── test_sidebar.py
│ ├── test_left_sidebar.py
│ ├── test_left_sidebar_global_history.py
│ ├── test_variables_panel.py
│ ├── test_snippet_panel.py
│ ├── test_debug_panel.py
│ └── test_saved_responses_panel.py
│ ├── test_saved_responses_panel.py
│ ├── test_request_history_panel.py
│ ├── test_global_history_panel.py
│ └── test_global_history_open_navigation.py
├── widgets/ # Shared component tests
│ ├── test_code_editor.py
│ ├── test_code_editor_folding.py
Expand Down Expand Up @@ -570,8 +599,7 @@ tests/
│ ├── test_environment_selector.py
│ └── test_environment_sidebar_panel.py
├── panels/ # Panel tests
│ ├── test_console_panel.py
│ └── test_history_panel.py
│ └── test_console_panel.py
└── request/ # Request/response editing tests
├── conftest.py # make_request_dict fixture factory
├── test_folder_editor.py
Expand Down
Loading
Loading