Skip to main content

Profile Builder (POC)

Location: development/.dev_testing/profile_editor/concept/concept_v3.py
Run: python development/.dev_testing/profile_editor/concept/concept_v3.py (port 8090)


Architecture

The builder uses a model-driven pattern. One dict (profile_model) is the single source of truth. The UI reads from it and writes back to it.

profile_model (dict)
↕ sync_model_from_ui() ← pulls current UI edits into the model
↕ editor_panel.refresh() ← rebuilds all UI elements from the model
↓ build_toml() → generates TOML string from the model
↑ parse_toml() ← parses TOML string into the model

Why model-driven

The original concept (v1) stored state inside NiceGUI elements — you had to walk card children to extract transforms. That works for build-only, but breaks when you need to load a profile: you can't populate UI elements that were created inline during a one-shot render.

The model lets us: parse TOML into a dict, then call editor_panel.refresh() to rebuild the entire UI from that dict. Same path for new profiles, loaded files, and future API integration.


Data Model

profile_model = {
"name": "My Custom Profile",
"author": "",
"proto": "tcp", # shared across GET and POST
"get": {
"body": "<METADATA>", # [raw.get] body template
"client_transforms": [ # [raw.get.client.metadata] transforms
{"op": "base64url"},
{"op": "prepend", "val": "GET /update?sid="},
],
"server_body": "<OUTPUT>", # [raw.get.server] body
"server_transforms": [], # [raw.get.server.output] transforms
},
"post": {
"body": "<OUTPUT>", # [raw.post] body template
"client_transforms": [], # [raw.post.client.output] transforms
"server_body": "", # [raw.post.server] body (ACK, no transforms)
},
}

Transform entries always use {"op": ..., "val": ...} internally, even for symcrypt (whose TOML field is key). The TOML generator maps val back to the correct field name (val or key) based on the TRANSFORMS dict.


Key Globals

NameTypePurpose
profile_modeldictCanonical profile state. Everything reads/writes here.
ui_refsdictMaps string keys to live NiceGUI elements (inputs, cards). Cleared on each editor_panel.refresh().
toml_textdictHolds the generated TOML string for the right-panel codemirror.
TRANSFORMSdictTransform metadata: description, field name, wire types, hints.

Data Flow

Build (GUI -> TOML)

  1. User edits fields in the GUI
  2. Clicks "Build Profile"
  3. sync_model_from_ui() reads all ui_refs elements and card children back into profile_model
  4. build_toml() generates the TOML string from profile_model
  5. preview_panel.refresh() updates the codemirror

Load (TOML -> GUI)

  1. User clicks "Load" -> picks a file, built-in profile, or pastes TOML
  2. parse_toml(text) parses via tomllib and populates a new profile_model
  3. editor_panel.refresh() rebuilds the entire left panel from the model
  4. Pre-populated transforms are added via add_transform(card, op, val)

TOML String Handling

This is the part most likely to cause bugs. The rules:

FieldTOML string type\xNN\r\nIn the GUI
bodyBasic "..."Not valid in TOMLParsed to real CR+LFShown as actual newlines in textarea
val / keyLiteral '...'Stays as literal textStays as literal textShown as-is in input field

Body round-trip: TOML \r\n -> parser -> real newlines -> textarea displays them -> _escape_toml_body() converts real newlines back to \r\n in the output. User-typed \xNN and \r\n literal sequences are protected from double-escaping via sentinel substitution.

Transform val round-trip: TOML '\xNN' -> parser -> literal text \xNN -> input shows it as-is -> _format_transform_val() wraps in single quotes as-is. No conversion needed.


Sortable Gotcha

card.make_sortable(handle=".drag-handle") must be called after pre-populating transforms into the card. SortableJS initializes on the DOM element — if called on an empty card before children are added in the same render batch, drag handles on pre-populated items won't bind. Transforms added later via button click work either way because SortableJS re-scans on mutation.


Files

FileWhat
concept.pyOriginal v0 POC — drag-sortable transforms only
concept_v1.pyAdded TOML generation, body fields, right-panel preview
concept_v3.pyModel-driven rewrite with TOML loading, file upload, refreshable panels