MANDATORY — EVERY code change MUST be followed by an instruction audit. After modifying, adding, or deleting ANY source file, test file, signal, TypedDict, service method, QSS objectName, or architectural pattern, you MUST review ALL instruction files listed below and update them to reflect the change. Stale or incomplete instructions are treated as bugs.
Checklist — run through each step after every code change:
- Update the architecture tree in this file to match
src/andtests/. Add new files, remove deleted files.- Update
architecture.instructions.mdwith any new or changed signals, data flows, TypedDicts, implicit contracts, or service methods.- Update
pyside6.instructions.mdwith any newobjectNamevalues used in global QSS.- Update
testing.instructions.mdwith any new test files or directories.- Update
sqlalchemy.instructions.mdwith any new models, relationships, or repository functions.- Update relevant skills (under
.github/skills/) when adding or changing signals, service/repository methods, TypedDicts, widgets, or parsers. See the Skills table below.- Search every instruction file and skill for stale references to renamed, moved, or deleted code. Remove or correct them.
- Update
docs/pages when adding, changing, or removing public API, signals, TypedDicts, widgets, parsers, or architectural patterns. Seedocs/contributing/updating-docs.mdfor the full checklist.
This file and the scoped instruction files below form a single source of truth.
- Check all instruction files for overlap before editing any of them.
- Never duplicate rules across files — reference the canonical location.
- Place rules in the most specific file that applies. Only add rules here if they are truly project-wide.
- Prefer creating new scoped instruction files (under
.github/instructions/with anapplyToglob) over adding to this file.
Scoped instruction files (auto-applied by path):
| File | Applies to |
|---|---|
| pyside6.instructions.md | src/ui/**/*.py |
| sqlalchemy.instructions.md | src/database/**/*.py |
| architecture.instructions.md | src/**/*.py |
| testing.instructions.md | tests/**/*.py |
| documentation.instructions.md | docs/**/*.md |
On-demand skills (loaded when the task matches the description):
| Skill | Description |
|---|---|
| signal-flow | Complete signal flow diagrams, signal declaration tables, MainWindow wiring summary |
| service-repository-reference | Repository function catalogues, service method tables, TypedDict schemas |
| widget-patterns | Tree badge rendering, data roles, InfoPopup, VariablePopup, theme module, new widget checklist |
| test-writing | Test patterns for all layers — repository, service, UI widget, MainWindow |
| import-parser | How to add a new import format parser to the import system |
| customization-guide | How to create, update, or debug Copilot instruction files, skills, applyTo patterns, and YAML frontmatter |
Instructions vs Skills: Instructions are always loaded when editing matching files — keep them lean with core rules. Skills are loaded on-demand when the task description matches — use them for heavyweight reference material, step-by-step guides, and catalogues.
If you need to add a new skill or instruction file, follow these
minimal rules (full guide in the customization-guide skill):
Skill — .github/skills/<name>/SKILL.md:
---
name: "<name>" # kebab-case, matches folder name
description: "One sentence ..." # VS Code matches this to user prompts
---
# <Title>
(content)Instruction — .github/instructions/<name>.instructions.md:
---
name: "<Display Name>"
description: "One sentence ..."
applyTo: "src/path/**/*.py" # glob — auto-loaded for matching files
---
# <Title>
(content)After creating either, update this file: add the new entry to the scoped-instructions or skills table above, and update the sync checklist if needed.
Postmark — native desktop API client built with PySide6, SQLAlchemy 2.0, Python 3.12+, managed by Poetry.
poetry install --with dev # pytest, ruff, mypy
poetry run python src/main.py
poetry run ruff check src/ && poetry run ruff format src/
poetry run mypy src/
poetry run pytestsrc/ is the source root for all tools (pythonpath, mypy_path,
extraPaths in pyproject.toml). Imports use bare module names:
from database.database import init_db.
Fastest paths to understand and navigate the codebase:
- All services at a glance: Read
src/services/__init__.py— re-exportsCollectionService,EnvironmentService,ImportService, and key TypedDicts (RequestLoadDict,VariableDetail,LocalOverride). - HTTP subsystem: Read
src/services/http/__init__.py— re-exportsHttpService,GraphQLSchemaService,SnippetGenerator,SnippetOptions,HttpResponseDict,parse_header_dict. Auth header injection lives insrc/services/http/auth_handler.py. OAuth 2.0 token exchange lives insrc/services/http/oauth2_service.py. - All DB models: Read
src/database/database.py— re-exports all four ORM models (CollectionModel,RequestModel,SavedResponseModel,EnvironmentModel). - Collection CRUD vs queries: Mutations live in
collection_repository.py; read-only tree/breadcrumb/ancestor queries live incollection_query_repository.py. - Signal flow: Load the
signal-flowskill for complete wiring diagrams. - TypedDicts: Cross-module dict schemas live in the service that owns
them (e.g.
RequestLoadDictincollection_service.py,HttpResponseDictinhttp_service.py). - Test fixtures:
make_collection_with_request(rootconftest.py) andmake_request_dict(tests/ui/request/conftest.py) reduce setup boilerplate.
docs/ # Project documentation (see docs/README.md)
├── README.md # Landing page + full table of contents
├── getting-started/ # Installation, running, overview
├── architecture/ # Layered design, data flow, directory tree
├── api-reference/ # Function signatures, TypedDicts, signals
│ ├── database/ # ORM models, repository functions
│ └── services/ # Service methods, HTTP, auth, parsers
├── ui-reference/ # Widget classes, styling, navigation
├── guides/ # How-to guides (import parser, auth, widget, tests, signals)
└── contributing/ # Coding conventions, testing, updating docs
src/
├── main.py # Entry point — QApplication + init_db()
├── database/ # Engine, models, repository
│ ├── database.py # init_db(), get_session(), migration
│ └── models/
│ ├── base.py # DeclarativeBase
│ ├── collections/
│ │ ├── collection_repository.py # CRUD for collections + requests
│ │ ├── collection_query_repository.py # Read-only tree/breadcrumb/ancestor queries
│ │ ├── import_repository.py # Atomic bulk-import of parsed data
│ │ └── model/
│ │ ├── collection_model.py # CollectionModel (folders)
│ │ ├── request_model.py # RequestModel (HTTP requests)
│ │ └── saved_response_model.py
│ └── environments/
│ ├── environment_repository.py # CRUD for environments
│ └── model/
│ └── environment_model.py # EnvironmentModel (key-value sets)
├── services/ # Service layer (UI ↔ DB bridge)
│ ├── collection_service.py # CollectionService (static methods)
│ ├── environment_service.py # EnvironmentService (variable substitution + TypedDicts)
│ ├── import_service.py # ImportService (parse + persist)
│ ├── http/ # HTTP request/response handling
│ │ ├── http_service.py # HttpService (httpx) + response TypedDicts
│ │ ├── graphql_schema_service.py # GraphQL introspection + schema parsing
│ │ ├── auth_handler.py # Shared auth header injection (all 12 auth types)
│ │ ├── oauth2_service.py # OAuth 2.0 token exchange (4 grant types)
│ │ ├── snippet_generator/ # Code snippet generation sub-package (23 languages)
│ │ │ ├── generator.py # SnippetGenerator, SnippetOptions, LanguageEntry, registry
│ │ │ ├── shell_snippets.py # cURL, HTTP raw, wget, HTTPie, PowerShell
│ │ │ ├── dynamic_snippets.py # Python, JS, Node, Ruby, PHP, Dart
│ │ │ └── compiled_snippets.py # Go, Rust, C, Swift, Java, Kotlin, C#
│ │ └── header_utils.py # Shared header parsing utility
│ └── import_parser/ # Parser sub-package
│ ├── models.py # TypedDict schemas for parsed data
│ ├── postman_parser.py # Postman collection/environment parser
│ ├── curl_parser.py # cURL command parser
│ └── url_parser.py # URL/raw-text auto-detect parser
└── ui/ # PySide6 widgets
├── main_window/ # Top-level MainWindow sub-package
│ ├── window.py # MainWindow widget + signal wiring
│ ├── send_pipeline.py # _SendPipelineMixin — HTTP send/response flow
│ ├── draft_controller.py # _DraftControllerMixin — draft tab open/save
│ ├── tab_controller.py # _TabControllerMixin — tab open/close/switch
│ └── variable_controller.py # _VariableControllerMixin — env variable + sidebar management
├── loading_screen.py # Loading screen overlay widget
├── sidebar/ # Right sidebar sub-package
│ ├── sidebar_widget.py # RightSidebar (icon rail) + _FlyoutPanel
│ ├── variables_panel.py # VariablesPanel — read-only variable display
│ ├── snippet_panel.py # SnippetPanel — inline code snippet generator
│ └── 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
├── styling/ # Visual theming and icons
│ ├── theme.py # Palettes, colours, badge geometry, method_color(), status_color()
│ ├── theme_manager.py # ThemeManager — QPalette + QSettings
│ ├── tab_settings_manager.py # TabSettingsManager — request-tab QSettings bridge (preview, limits, activate-on-close, wrap mode)
│ ├── global_qss.py # build_global_qss() — global stylesheet builder
│ └── icons.py # Phosphor font-glyph icon provider (phi())
├── widgets/ # Reusable shared components
│ ├── code_editor/ # CodeEditorWidget sub-package
│ │ ├── editor_widget.py # CodeEditorWidget — main editor class
│ │ ├── highlighter.py # Syntax highlighting engine
│ │ ├── folding.py # Code folding logic
│ │ ├── gutter.py # Line-number gutter
│ │ └── painting.py # Custom painting helpers
│ ├── info_popup.py # InfoPopup (QFrame) base + ClickableLabel
│ ├── key_value_table.py # Reusable key-value editor widget
│ ├── variable_line_edit.py # VariableLineEdit — QLineEdit with {{var}} highlighting + hover popup
│ └── variable_popup.py # VariablePopup — singleton hover popup for variable details
├── collections/ # Collection sidebar
│ ├── collection_header.py
│ ├── collection_widget.py
│ ├── new_item_popup.py # NewItemPopup — Postman-style icon grid popup
│ └── tree/ # Tree widget sub-package
│ ├── constants.py
│ ├── draggable_tree_widget.py
│ ├── collection_tree.py # CollectionTree widget
│ ├── tree_actions.py # _TreeActionsMixin — context menus, rename, delete
│ └── collection_tree_delegate.py # Custom delegate for method badges
├── dialogs/ # Modal dialogs
│ ├── collection_runner.py
│ ├── import_dialog.py
│ ├── save_request_dialog.py # Save draft request to collection
│ └── settings_dialog.py # Settings (theme + request-tab behaviour)
├── environments/ # Environment management widgets
│ ├── environment_editor.py
│ └── environment_selector.py
├── panels/ # Bottom / side panels
│ ├── console_panel.py
│ └── history_panel.py
└── request/ # Request/response editing
├── folder_editor.py # Folder/collection detail editor
├── http_worker.py # HttpSendWorker + SchemaFetchWorker (QThread)
├── auth/ # Shared auth sub-package (14 auth types)
│ ├── auth_field_specs.py # Per-type FieldSpec definitions (AUTH_FIELD_SPECS)
│ ├── auth_mixin.py # _AuthMixin — shared by both editors
│ ├── auth_pages.py # FieldSpec dataclass, page builders, auth constants
│ ├── auth_serializer.py # Generic load/save for all auth types
│ └── oauth2_page.py # OAuth 2.0 custom page (grant-type switching)
├── request_editor/ # RequestEditor sub-package
│ ├── editor_widget.py # RequestEditor — main request editing widget
│ ├── auth.py # Re-export of _AuthMixin from auth sub-package
│ ├── body_search.py # _BodySearchMixin — search/replace in body
│ └── graphql.py # _GraphQLMixin — GraphQL mode + schema
├── response_viewer/ # ResponseViewer sub-package
│ ├── viewer_widget.py # ResponseViewer — response display widget
│ └── search_filter.py # _SearchFilterMixin — response search/filter
├── navigation/ # Tab switching and path navigation
│ ├── breadcrumb_bar.py
│ ├── request_tab_bar.py # Compatibility wrapper re-exporting the wrapped deck
│ ├── request_tabs/ # Wrapped multi-row request tab deck sub-package
│ │ ├── __init__.py
│ │ ├── bar.py # RequestTabBar custom wrapped-row deck
│ │ ├── labels.py # TabLabel / FolderTabLabel chip content widgets
│ │ └── tab_button.py # TabButton chip with close + reorder interactions
│ └── tab_manager.py # TabManager + TabContext (with local_overrides, draft_name)
└── popups/ # Response metadata popups
├── status_popup.py # HTTP status code explanation
├── timing_popup.py # Request timing breakdown
├── size_popup.py # Response/request size breakdown
└── network_popup.py # Network/TLS connection details
tests/
├── conftest.py # Autouse fresh-DB fixture + qapp fixture + tab-settings reset
├── unit/ # Repository & service layer tests
│ ├── database/ # Repository tests
│ │ ├── test_repository.py
│ │ └── test_environment_repository.py
│ └── services/ # Service layer tests
│ ├── test_service.py
│ ├── test_environment_service.py
│ ├── test_import_parser.py
│ ├── test_import_service.py
│ └── http/ # HTTP service tests
│ ├── test_http_service.py
│ ├── test_graphql_schema_service.py
│ ├── test_snippet_generator.py
│ ├── test_snippet_shell.py
│ ├── test_snippet_dynamic.py
│ ├── test_snippet_compiled.py
│ ├── test_auth_handler.py
│ └── test_oauth2_service.py
└── ui/ # End-to-end PySide6 widget tests
├── conftest.py # _no_fetch (autouse) + helpers
├── test_main_window.py
├── test_main_window_tabs_navigation.py # Wrapped tab deck shortcuts + search tests
├── test_main_window_save.py # SaveButton + RequestSaveEndToEnd tests
├── test_main_window_draft.py # Draft tab open/save lifecycle tests
├── test_main_window_session.py # Tab session persistence (save/restore) tests
├── styling/ # Theme and icon tests
│ ├── test_theme_manager.py
│ └── test_icons.py
├── sidebar/ # Sidebar widget tests
│ ├── test_sidebar.py
│ ├── test_variables_panel.py
│ ├── test_snippet_panel.py
│ └── test_saved_responses_panel.py
├── widgets/ # Shared component tests
│ ├── test_code_editor.py
│ ├── test_code_editor_folding.py
│ ├── test_code_editor_painting.py
│ ├── test_code_editor_memory.py
│ ├── test_info_popup.py
│ ├── test_key_value_table.py
│ ├── test_variable_line_edit.py
│ ├── test_variable_popup.py
│ └── test_variable_popup_local.py
├── collections/ # Collection sidebar tests
│ ├── test_collection_header.py
│ ├── test_collection_tree.py
│ ├── test_collection_tree_actions.py
│ ├── test_collection_tree_delegate.py
│ ├── test_collection_widget.py
│ └── test_new_item_popup.py
├── dialogs/ # Dialog tests
│ ├── test_import_dialog.py
│ ├── test_save_request_dialog.py
│ └── test_settings_dialog.py
├── environments/ # Environment widget tests
│ ├── test_environment_editor.py
│ └── test_environment_selector.py
├── panels/ # Panel tests
│ ├── test_console_panel.py
│ └── test_history_panel.py
└── request/ # Request/response editing tests
├── conftest.py # make_request_dict fixture factory
├── test_folder_editor.py
├── test_http_worker.py
├── test_request_editor.py
├── test_request_editor_auth.py
├── test_request_editor_binary.py
├── test_request_editor_graphql.py
├── test_request_editor_search.py
├── test_response_viewer.py
├── test_response_viewer_search.py
├── navigation/ # Tab and breadcrumb tests
│ ├── test_breadcrumb_bar.py
│ ├── test_request_tab_bar.py
│ └── test_tab_manager.py
└── popups/ # Response popup tests
├── test_status_popup.py
├── test_timing_popup.py
├── test_size_popup.py
└── test_network_popup.py
Layering: UI → signals → Service → Repository → get_session().
UI must never import from database/.
After any code change, run the full validation suite and confirm zero failures before considering the task complete:
poetry run pytest # all tests must pass
poetry run ruff check src/ tests/ # linter clean
poetry run ruff format --check src/ tests/ # formatter clean
poetry run mypy src/ tests/ # type checker cleanZERO tolerance for errors — including pre-existing ones. Every command above must exit with zero errors, warnings, or suggestions. If you find a pre-existing error (lint, type, format, test failure) while working on an unrelated task, fix it immediately in the same change. "It was already broken" is never an acceptable excuse — fix it anyway. All four commands passing clean is a hard gate on every change. No exceptions.
NEVER use --fix or auto-format as a substitute for the checks above.
Always run the check-only commands first. If they fail, fix the code
manually (or with --fix), then re-run the check-only commands and
confirm they pass. The goal is to surface every issue visibly — a silent
auto-fix that is never re-verified can leave the working tree clean while
the staged/committed version is still broken.
After any documentation change (.md files, instruction files, README),
run the markdown link checker and confirm zero broken links:
python scripts/check_md_links.pyNever skip a layer — repository, service, UI, and MainWindow tests all
must stay green. See testing.instructions.md for detailed conventions.
from __future__ import annotationsin every module.X | None, notOptional[X].- Ruff is the linter and formatter (config in
pyproject.toml). First-party packages for isort:database,ui,services. - Named constants over magic numbers.
init_db()must be called before any DB access (app startup and test fixture).- Every module, class, and public function must have a docstring.
- All hex colour values belong in
src/ui/styling/theme.py-- never inline. - Use
TypedDictfor dict schemas that cross module boundaries. - No emoji in code comments -- use plain numbered steps (e.g.
# 1.). - Directory file limit: No directory may contain more than 5
.pyfiles (excluding__init__.py). When a directory reaches this limit, group related files into a sub-package before adding more. Test directories mirror the source tree; test file count may exceed 5 when multiple test files cover a single source module. - File line limit: No single
.pyfile may exceed 600 lines (including docstrings and comments). When a file approaches this limit, extract cohesive groups of methods, helper classes, or setup logic into a sub-package. Re-export public symbols from the package's__init__.pyso external imports remain stable. Test files follow the same limit — split by test class into separate files mirroring the sub-package.