001 Notes
Desktop Local App Lessons
UI event loop
local storage
settings
worker jobs
parsers
schedulers
logs
generated files
browser sessions
tray lifecycle
shutdown behavior
the app works for demo data
then freezes, hides errors, leaks files, or leaves background work running
1. The Desktop Runtime Mental Model
visible state:
what the user sees now
durable state:
local database, config, user files, saved records
runtime state:
workers, timers, browser sessions, sockets, caches, queues
UI shows "updated"
database write failed
worker is still running
timer will retry in the background
current file/database
last import status
active worker count
last parser error
next scheduled run
cache/log locations
shutdown status
If local state exists, the user should be able to inspect it.
If background work exists, the user should know whether it is running.
If generated files exist, the user should be able to delete them.
2. Main Thread Discipline
paint
layout
input handling
small state updates
signal dispatch
cheap model queries
network requests
HTML parsing
CSV imports
database migrations
large table filtering
file scans
image processing
AI/model inference
browser automation
long compression/export work
user clicks Import CSV
UI parses 200,000 rows on main thread
window freezes
OS says app is not responding
flowchart LR
A[User clicks Import] --> B[Create import job]
B --> C[Worker parses file]
C --> D[Progress signal]
C --> E[Preview result]
E --> F[UI applies result]
F --> G[User confirms batch]
anything slow, blocking, or unbounded leaves the UI thread
paintEvent
resizeEvent
data()
selectionChanged
textChanged
timer callbacks
scroll handlers
3. Worker Job Contract
| Field | Purpose |
|---|---|
| job id | ignore stale completions |
| input snapshot | prevent live UI state mutation |
| progress | keep user informed |
| cancel flag | stop wasted work |
| result object | apply safely on UI thread |
| error payload | show actionable failure |
| cleanup hook | close files/processes/sessions |
worker reads current UI fields while running
worker mutates widgets directly
worker raises generic exception
worker cannot be cancelled
UI gathers current form values
UI creates immutable request
worker processes request
worker emits progress/result/error
UI thread applies result if job is still current
current_job_id = 42
result.job_id = 41
if result.job_id != current_job_id:
ignore result
search filters
scraper runs
imports
dashboards
background refresh
browser automation
selection detail panels
4. PyQt Virtualized Tables
for row in rows:
table.addWidget(row_widget)
QAbstractTableModel stores row data
QTableView asks only for visible cells
delegate paints visible cells
worker prepares expensive values
flowchart TD
A[Backing records] --> B[QAbstractTableModel]
B --> C[QTableView]
D[Delegate] --> C
E[Worker filter/sort/import] --> A
F[SQLite/JSON/File store] --> A
| Part | Responsibility |
|---|---|
| model | expose row/column data cheaply |
| view | scrolling, selection, focus |
| delegate | paint/edit visible cells |
| worker | expensive filter/sort/import |
| storage | SQLite/JSON/files as needed |
data(index, role) must be cheap
data():
parse date
format currency with slow locale lookup
query database
fetch URL
read file
data():
return precomputed display value
return placeholder if expensive value is not ready
5. Sorting And Filtering
flowchart LR
A[User types filter] --> B[Debounce]
B --> C[Worker computes matching row ids]
C --> D[Model swaps visible mapping]
D --> E[View repaints]
all_rows = source records
visible_rows = row ids matching current filter
source_row = visible_rows[view_row]
showing 1,284 of 92,000 records
filter: status=open, source=remote, text="python"
new filter typed -> cancel old filter job
6. Import Auditability
parse file -> insert rows silently
parse file
show preview
show parse errors
show duplicate candidates
create import batch
allow rollback/delete batch
| Field | Why |
|---|---|
| import batch id | rollback and audit |
| source filename | trace origin |
| source row number | explain parse errors |
| raw row text | debug normalization |
| normalized fields | display final meaning |
| parse status | separate valid/invalid rows |
| duplicate key | prevent repeated imports |
| created record id | link source to output |
flowchart TD
A[Source file] --> B[Parse preview]
B --> C[Normalize fields]
C --> D[Detect duplicates]
D --> E[Show errors and warnings]
E --> F{User confirms?}
F -- no --> G[No durable change]
F -- yes --> H[Create import batch]
H --> I[Insert records]
I --> J[Batch can be reviewed or rolled back]
if import goes wrong, user can inspect and undo
7. Parser Health
job site changes HTML
bank changes SMS format
chat export format changes
price page moves value into script tag
CVE feed changes schema
last successful parse
last failed parse
parse version
records found
records rejected
error sample
next retry
source freshness
empty result list
Parser failed: salary field selector matched 0 items.
Last good run: yesterday.
Source page changed or parser needs update.
source_id
parser_version
last_success_at
last_failure_at
last_error_kind
last_error_sample
records_seen
records_accepted
records_rejected
8. Scraping: Direct HTTP vs Browser Session
request page/API
parse HTML/JSON
store result
site is static
API exists
authentication is simple
JavaScript rendering is not required
launch browser/profile
load page
wait for dynamic content
extract DOM
close cleanly
session state matters
JavaScript renders content
login/cookies are required
direct HTTP is blocked
show browser profile/cache size
show session/login status
stop browser on exit
kill child process if needed
clear session data from UI
surface extraction errors
parser version
source version
last sample captured
health status
user-visible error
manual retry
9. Expense Manager Example
CSV/bank export
-> parse preview
-> normalize date/amount/currency
-> detect duplicate
-> user confirms
-> create import batch
-> optional category suggestions
merchant: "Metro Cafe"
suggested category: Food
confidence: medium
amount
date
currency
account
duplicate status
| Failure | Correct behavior |
|---|---|
| invalid date | row stays in preview error list |
| duplicate transaction | show duplicate candidate |
| unknown currency | ask user or reject row |
| changed CSV columns | mapping screen appears |
| partial import failure | batch status shows failed rows |
the user can explain every transaction's source
10. Job Scraper Example
source registry
query configuration
fetch policy
parser
dedupe
status tracking
review UI
source
query
listing id or URL
title
company
location
date seen
status
parse version
last parse error
dedupe key
| Field | Meaning |
|---|---|
| last successful run | freshness |
| last failed run | reliability |
| listings found | output volume |
| parse errors | source drift |
| duplicates skipped | dedupe behavior |
| next scheduled run | scheduling visibility |
11. Price Tracker Example
flowchart LR
A[Tracked item] --> B[Schedule check]
B --> C[Fetch page or API]
C --> D[Parse price]
D --> E[Store history]
E --> F{Threshold crossed?}
F -- yes --> G[Notify user]
F -- no --> H[Update last checked]
last checked
next check
last error
current price
target price
history chart
notification status
parser/source health
unknown price
parser failed
source blocked
network unavailable
12. CVE Checker Example
| Field | Why |
|---|---|
| advisory source | trust and provenance |
| last database update | staleness |
| package normalization | match quality |
| version parser | correctness |
| match reason | explain result |
| unknown state | avoid false safety |
No CVEs found. Safe.
No matching advisories in the local database.
Database age: 9 days.
2 package names could not be normalized.
13. Kanban Example
| Question | Product decision |
|---|---|
| where is board stored? | show location in settings |
| when does autosave happen? | visible autosave state |
| what if file changes externally? | reload/conflict prompt |
| is there undo? | bounded undo stack |
| are backups created? | visible backup cleanup |
autosave failed silently
Autosave failed: permission denied.
Board has unsaved changes.
Save As...
14. Dashboards
poll everything every second
| Lane | Examples |
|---|---|
| fast | cheap local process checks |
| medium | normal APIs |
| slow | expensive or rate-limited APIs |
| manual | destructive or costly actions |
loading
fresh
stale
error
empty
permission needed
rate limited
network budget
CPU budget
API quota budget
attention budget
updated 12s ago
stale for 18m
last error: timeout
15. Tray Tools
single-instance behavior
clear running/stopped state
visible logs
settings window
quit action
clean shutdown
menu shows server status
menu has stop server action
quit stops server
startup does not silently expose port
orphan process
hidden port
stale lock file
config path unknown
logs hidden
autostart hard to disable
what is running?
where is config?
where are logs?
how do I stop it?
16. Browser-Backed Desktop Tools
cookies
cache
local storage
browser profile
download directory
session state
database size
cache size
browser profile size
logs size
clear cache
clear browser profile
clear local messages
export settings
native local database
embedded web session
hydrated native UI state
user-visible exports
17. PyQt vs egui
native widgets
large tables
dialogs
menus
OS integration
signals/slots
state is compact
rendering is rebuilt from state every frame
you want explicit state structs
layout is tightly controlled
do not do heavy work during rendering
do not block data()
do not block paintEvent()
do not block selection handlers
do not block update()
do not recompute expensive tables every frame
do not run network/file work in the frame loop
18. Local State And Paths
config files
SQLite databases
JSON settings
logs
cache files
browser profiles
downloaded indexes
exports
screenshots
model files
temporary files
config directory
data directory
cache directory
state/log directory
where data is stored
how large it is
what it is for
what happens if deleted
19. Generated Data Cleanup
| Action | Risk |
|---|---|
| clear thumbnail cache | low, regenerate later |
| clear logs | removes diagnostics |
| clear browser profile | logs user out |
| clear downloaded index | feature stale/unavailable until refresh |
| delete import batch | removes user records |
| delete exports | deletes user-created files |
category
size
path
purpose
delete consequence
action button
last modified
20. Settings
runtime paths
worker limits
poll intervals
parser source settings
notification preferences
theme/display options
privacy/storage options
import/export controls
startup tutorial reset
edit hidden config file by hand
restart and hope it worked
invalid interval rejected
missing executable shown as warning
unwritable path rejected
conflicting options explained
21. Logging
startup
settings load/save
import start/finish/failure
parser failure
scheduler run
worker cancellation
browser session start/stop
database migration
shutdown
API tokens
cookies
auth headers
full bank transaction exports
private chat content
local-only sensitive paths when exported
open log file
copy diagnostic summary
delete logs
export support bundle with redaction
22. Shutdown
stop timers
cancel workers
wait briefly
close databases
flush logs
stop browser/session processes
stop local servers
save settings
remove lock files
Stopping background import...
Closing browser session...
Flushing database...
close during import
close during scrape
close during scheduled poll
close during database write
close while browser is open
close while local server is running
no hidden worker or child process survives normal exit
23. Implementation Blueprint
UI:
views, actions, dialogs, status
model/store:
local records, settings, generated data metadata
workers:
imports, scans, parsing, network, cleanup
scheduler:
next run, last run, retry state
parser:
versioned extraction logic, health report
storage governance:
size, location, delete/rebuild actions
main window
table model
storage functions
worker jobs
parser functions
scheduler state
Controller -> Service -> Manager -> Provider -> Client
24. What To Measure
startup time
time to first usable UI
import time
filter latency
scroll responsiveness
database size
cache/log size
worker cancellation time
shutdown time
parser success rate
last scheduler lag
rows visible
rows total
filter time
paint jank
memory usage
fetch duration
parse duration
records accepted
records rejected
duplicate count
last error
server uptime
active port
last request
shutdown success
child process count
25. What Was Done And Why
26. What Was Used, Removed, Or Deferred
worker threads/processes
virtualized tables
cheap model data contract
debounced filtering
import previews
batch ids and rollback
parser health
scheduler visibility
local SQLite/JSON state
browser-session lifecycle controls
generated-data cleanup
tray lifecycle controls
visible settings and logs
clean shutdown
widget-per-row tables
main-thread imports
main-thread scraping
silent parser failures
hidden infinite polling loops
hardcoded paths
undeletable caches
orphan browser/server processes
wrapper layers that only forward calls
distributed sync
enterprise policy systems
generic app frameworks
shared abstractions across unrelated tools
cloud observability stack
Comments