Originally recorded 2026-09-18 from locally available ctui conversations and Git history since August 22, 2026 (owner-approved cutoff). Private transcripts and conversations on other laptops were not repository dependencies. Dates below identify discussion/implementation. “Accepted” denotes explicit user direction; “Implemented policy” denotes documented codebase policy. Later entries supersede earlier choices where noted. Proposals remain in STATUS.
Read entries relevant to the task; this is durable rationale, not startup
reading in full. Personal submission preferences now live in the global
Codex instructions; their September 19 update is preserved in Git (88df143).
Accepted, Aug 22–26; 46eb6a8, 806187f, 7a46034.
Support tools such as Modbus clients/servers/proxies and network interaction
or testing applications. Prefer a Pythonic CtuiApp subclass with decorated
methods, sync/async commands, lifecycle hooks, events, and testable headless
dispatch. UI and CLI share the command engine. Keep service and layout
replacement possible. Examples should be short, ordered, and accessible to
new Python developers; use self.events rather than implicit ctx injection.
Accepted, Aug 23 and reaffirmed Sep 13; 806187f, bfe795c.
Every application entry point supports full-screen and automatic CLI operation through one dispatcher, avoiding a separate application-owned CLI parser. Routing, batch ordering, lifecycle, and result contracts live in CtuiApp; examples remain in the README.
The owner explicitly did not require compatibility with earlier ctui releases.
Historical Ctui, do_ conventions, instance command registration, and special
legacy result handling were removed during the overhaul. Use current tutorials
rather than reviving old examples. Correction from implementation review on
Sep 18: commands return str or CommandResult; bare None is rejected by
current dispatch and its regression test. The earlier note saying it remained
valid was stale. Use CommandResult.success() for no-output success.
Implemented policy: documented top-level exports and ctui.widgets are the
supported 1.x surface; internal submodules are not generally stable. Follow
semantic versioning and document deprecations before future major removal.
The import boundary is documented in init.py.
Accepted, Sep 13; b031e15, 213043a.
Keep positional parameters as the default; named options require explicit
Argument(flags=...) opt-in. Conversion follows function annotations, avoiding
a competing Argument(type=...) source of truth. Parameter help or completion
metadata alone does not change how users supply a value. See
Argument, command, and Command.parse_args for syntax,
validation, quoting, and error contracts.
This supersedes the Aug 26 preference against -- options. Quoted Windows-path
regressions (420daed) preserve shell-like tokenization across platforms.
Accepted, Aug 23–27; 9a7611c, d106e49, 4aeb8e6, d307e0b.
Completion should explain the argument currently being entered, advance only at an explicit token boundary, and show subcommands alongside parent arguments. Allow unique command/choice abbreviations while rejecting ambiguity. Put error locations back in the original input so UI users can correct it and CLI users can identify the bad argument.
Accepted follow-up, October 6: comma-separated list arguments keep element completion active after commas. ctui excludes exact matches already typed across the list, without prefix-filtering partial entries. Providers return individual candidates with an empty context word; ctui owns filtering and preserves earlier elements when inserting. Lists do not participate in dispatch-time unique-prefix expansion, preventing suggestions from rewriting supplied list values.
Detailed contracts belong in completion.py, commands.py, and application.py. Sep 18 completion fixes retain non-inserting hints through prompt-toolkit filtering and expand earlier named choice prefixes; the local rationale is in completion/layout docstrings.
Accepted follow-up, September 21: report supplied-argument errors in command-line order across positional and named arguments. Whole-line tokenization errors still come first, and missing required arguments remain end-of-input errors.
Accepted follow-up, September 21: provide a reusable, explicitly configured
PathCompleter rather than inferring filesystem completion from Path types.
Use it for built-in file arguments and the filesystem example. Keep directory
navigation available with file filters; suggestions do not restrict execution.
Project import does not filter by extension because snapshot suffixes are optional.
Accepted follow-up, October 20: Tab accepts the highlighted candidate (the first when none is selected), rather than cycling or expanding a common prefix. Acceptance inserts/reuses a separator for commands and scalar/file arguments, then requests the next context's suggestions. Directories continue browsing, including directory-only arguments; comma-separated lists remain editable. Quoted continuing values keep the cursor before a completion-owned closing quote. Space finishes that argument; a matching typed quote reuses the inserted quote. Manual quoted editing remains normal until completion is accepted. Preserve quote style and subsequent arguments. Browser clicks and terminal Tab use shared accepted edits; previews and non-inserting hints remain separate. Keep PathCompleter and its existing directory/filter configuration rather than introducing separate file/folder completers. Filters remain advisory. Supersede implicit dispatch-time prefix expansion for PathCompleter arguments: paths stay literal unless explicitly completed, preventing automatic descent into a sole child and replacement of new output filenames.
Accepted follow-up, October 23: at empty/whitespace-only input or an unquoted, unescaped command-word boundary, Tab reveals a hidden menu without inserting text and highlights its first row. A subsequent Tab accepts the selected row; Up/Down retain selection behavior. Escape dismisses visible/pending menus, and repeated Tab during loading does not queue acceptance of an unseen command. Use menu state rather than counting key presses. Partial words and argument values retain the October 20 acceptance behavior. Both frontends use the shared command-boundary decision, including help targets; moving to the beginning of existing text does not activate the empty-input policy.
Accepted follow-up, October 23: command names and aliases accept repeated whitespace between words consistently with argument parsing. Keep longest exact command matching and unique-prefix behavior. Exact-name resolution splits only the leading command words, preserving the raw argument tail for quoting, completion and original-input error positions.
Accepted, Aug 23; 84679d3, 1b53da9.
Leave selection and clipboard operations to the terminal, with mouse capture disabled by default and command input retaining focus. Earlier internal copy/paste bindings and their example were deliberately removed. The temporary request for Ctrl-C to exit was explicitly reversed: it clears input, while Ctrl-L clears output. Binding details live in keybindings.py; focus-preserving scroll mechanics live in functions.py.
Accepted follow-up, October 6: keep new terminal output at the first column of its final line, preserving long unwrapped lines. Alt-Left/Right scroll output horizontally in terminal and browser modes while command input retains focus.
Accepted follow-up, October 20: Alt-Up/Down replace Ctrl-Up/Down for one-line output scrolling in terminal and browser modes, aligning vertical navigation with Alt-Left/Right. Command input retains focus; unmodified Up/Down retain history/completion navigation.
Accepted follow-up, October 6: scoped terminal output protection queues Python stdout/stderr through prompt-toolkit run_in_terminal, preserving diagnostics without overwriting the full-screen renderer. Redirect existing handlers bound to the original standard streams; preserve logging configuration and other destinations. Restore streams/handlers and drain pending output on exit. CLI and web lifecycle behavior stays unchanged; direct OS writes are outside the contract.
Accepted follow-up: built-in history and project commands, including project configs, present successful output in titled scrollable MessageDialogs in TUI and WUI. Finish actions, persistence, history and events before presentation. Preserve output updates and queue results in completion order; WUI dialogs belong to the submitting tab. Dismissal/disconnection never rolls back completed work. CLI prints results normally. clear/help/exit keep their established behavior. Application commands may opt in with result_title; ordinary output is unchanged.
Accepted follow-up, October 23: remove the top-level clear command because
Ctrl-L already clears output in terminal and browser modes. Keep the shortcut,
programmatic CommandResult(clear_output=True) support, and project history
clearing commands. This supersedes the output-clear command exception above.
Apply output appends when commands finish to avoid overwriting concurrent results with stale snapshots. See CommandResult. Progress tracking stays application-owned; the progress tutorial explains updates and cleanup. Custom layouts and application-wide shortcuts remain supported.
Accepted, Aug 26; 7a46034.
Runtime clients, sockets, servers, and tasks belong on application attributes. Named persistent profiles belong in configs; sent/received protocol data belongs in records, grouped by sessions. Generic storage remains injectable rather than being the primary home for these distinct concepts.
A stable app_id enables default disk persistence, with replaceable service implementations and platform-appropriate paths. Constructor defaults and service contracts belong in application.py and services.py; the concrete backend is documented in projects.py.
Accepted, Aug 26; 7a46034, fd3e743.
Use a SQLite database per project for portable snapshots, with UUID identity, a catalog, and a separate active-selection file. Export profiles as versioned JSON; TOML was discussed for display, not chosen for exchange. Allow application config templates. Python 3.11 became the minimum.
Accepted follow-up, October 6: startup always opens the project named default,
creating it when absent even if other projects exist. Saved projects require an
explicit load; restoring the last active selection is superseded. Default data
persists, and saveas still activates its copy for the current run. state.json
records the current selection but does not determine startup.
Accepted follow-up, October 6: expose configuration commands under
project configs, including list/show/export/import/reset. Remove unreleased
top-level configs command names without compatibility aliases. Retain
self.configs and its API, project persistence, JSON exchange format and reset
confirmation; the user command hierarchy reflects project ownership.
Deletion is permanent by explicit preference. Destructive commands require a formatted confirmation message so approval names the affected data. Command history can be suppressed, especially for project switching and resets. Export history as its own discoverable command producing a replayable UTF-8 file. Protocol-specific record commands remain application-owned. Detailed service, exchange, and command contracts live in projects.py and commands.py.
Accepted follow-up, October 23: move history display/search/export under
project history, with project history clear and project reset history
sharing one confirmed reset operation. Remove all top-level history command
names without aliases. Commands target the active project's backend history,
including when app.history is overridden. Preserve display/export recording
after reading prior entries; search and clearing suppress their own entries.
Apps without a project backend retain internal memory history and input recall
without built-in history commands; their history service remains accessible
programmatically. This resolves the Sep 18 discrepancy and supersedes earlier
choices about exposing history clearing at the top level.
Accepted, Sep 13; 7921344, f6b3b98.
Use aiosqlite to keep SQL work off the event loop, superseding the Aug 26 standard-library workaround. The earlier observed hang is not an established incompatibility or a reason to remove the tested async backend. Dependency versions belong in packaging metadata and the lockfile; Python 3.11 remains the baseline.
Protect persisted work through integrity/compatibility checks, explicit ordered migrations, transactional updates, and backups before migration. Reject newer schemas or missing migration paths rather than guessing. Keep an existing project usable when candidate validation fails and clean failed imports out of the catalog. Mechanisms and limits, including the scope of asynchronous I/O, are documented in SqliteProjectBackend.
Accepted, Sep 13; 1cc1d8e, dfa4d88, 64f813c, 3fb8ff9.
Use dedicated parameter annotations for reusable conversion and validation, keeping annotations the single source of type information (D03). Represent patterns and integer ranges compactly so argument conversion cannot eagerly allocate enormous result sets before application limits can run. Expansion is explicit, lazy, and bounded; random sampling is a separate operation. Keep the pattern language finite and predictable instead of adopting a general regex engine. Align concrete and fuzzy hex formatting to avoid conflicting grammars.
Accepted documentation placement, Sep 18: keep per-type syntax, limits, semantics, and local rationale in class and method docstrings in types.py. Keep shared architectural rationale here; README and tutorials provide usage.
Accepted implementation, Sep 13–18; 63e4c81, b6ae3be, bfe795c,
c2b944f, 351436a, 1f9707d, dc6e1ef, 7e4e971.
CI targets Python 3.11–3.14 across Ubuntu x86-64, Ubuntu ARM64, Windows, and macOS. Source checks use unittest, Black, and isort. Build and install-test both wheel and sdist in isolated environments. A single portable Python distribution serves the platforms; tests do not imply separate OS packages.
Use matching PEP 440 package versions and Git tags: 1.0.0rc1 / v1.0.0rc1.
The tag workflow validates the match, runs checks, builds and smoke-tests,
attests, publishes via trusted PyPI publishing, and creates a GitHub release.
Merging alone does not publish. Prereleases are marked accordingly and are
not the latest stable release. Notes combine curated changelog content and
generated GitHub notes; RC notes may fall back to the base-version section.
The overhaul was merged through PR #6 before the local RC tag was created. See the release checklist for subsequent releases. Successful external publication is a verification item, not implied by this decision.
Owner update, September 18: RC1 was successfully published to PyPI and is
functional. More testing and changes are required, so the next release will
be RC2. The specific changes/tests are still to be listed. A background-job
manager and py.typed remain optional ideas, not release requirements.
Accepted follow-up, October 23: the next release is stable 1.0.0, dated
2026-10-23, superseding the earlier RC2 plan. The owner reports testing complete
and requested the dated stable changelog section. Metadata preparation, final
release verification, tagging and publication remain separate release steps.
Implemented metadata policy: retain the repository's existing
GPL-3.0-or-later license and include LICENSE in distributions. Metadata was
aligned with existing license/header evidence, not a newly approved relicensing
decision. No license change was accepted in the reviewed conversations.
Accepted, Sep 18.
Maintain root AGENTS.md for ctui-specific operating instructions, docs/STATUS.md for
current work/next steps, and this file for durable decisions and rationale.
Update them alongside meaningful work and transfer them with the working
branch. Keep the evidence and outstanding questions visible; neither a new
session nor another laptop should need the original private transcripts.
Accepted, September 18, during RC2 development.
UI help belongs in a scrollable popup that preserves output and restores focus; CLI help prints normally. Use one hierarchical reference with separate, customizable interface guidance so applications do not maintain two command manuals. Main help starts with welcome text, then interface guidance, then root commands/groups; targeted help omits the introduction.
Follow-up accepted Sep 18: read-only message/confirmation dialogs keep buttons focused during scrolling, so Enter activates the selected button. This supersedes the initial help-text focus and Tab-to-Ok interaction. Text-entry dialogs continue to focus input. Detailed hierarchy, rendering, and focus contracts live in help.py, application.py, and dialogs.py.
Accepted September 18: keep the dialog classes and convenience wrappers for
application developers, including text-input and callback-based dialogs.
Remove obsolete functions.show_help, unused Commands.descriptions, and
redundant Command.string, string_parts, and func_name. Keep active command
descriptions (desc/description) used by generated help and completion.
The obsolete helper was the only tabulate consumer, so remove that dependency.
Accepted follow-up, October 7: expand embedded dialogs with button choices, radio/checkbox selection and a fixed-key dictionary value editor. Keep ctui's composition of public upstream widgets within the existing application, rather than shortcut functions that construct a separate Application. Replace the copied dialog Button with a small upstream subclass; preserve caption sizing and scroll bindings. Retain the declared prompt-toolkit minimum with a browser fallback for ConditionalContainer versions without alternative_content.
Use one modal queue per terminal application or browser view, including existing help, confirmations and results. Remove floats before restoring focus and track convenience tasks for shutdown. Escape acknowledges messages, declines Yes/No, and returns None for cancellable value dialogs. Reserve None from button/list values; an accepted empty checkbox selection is []. Selection tuple ordering follows upstream: buttons (label, value), lists (value, label).
Dictionary dialogs edit existing string keys only, preserve order and return a new dictionary. Limit values to str/int/finite float/bool; DictField metadata supports labels, help, type overrides for initial None and validators. Boolean fields are checkboxes. Validate converted values synchronously in both UIs: True/None accepts, False/error text rejects inline while retaining edits and focusing the input. Field validators run before whole-form validation; unexpected exceptions propagate to the calling application. Dialogs collect values only; application code owns persistence and subsequent actions. Usage is documented in README.md.
Accepted presentation follow-up: dictionary labels and values share aligned rows in both UIs, with a divider, muted labels, distinct editable fields and visible focus. Put help below the corresponding value; keep booleans in the value column and labels outside Tab navigation. Keep the other dialogs' existing layouts and the dictionary API, conversion, validation and cancellation behavior.
Accepted, September 18, extending the type-docstring work in D09.
Class, method, and module docstrings own syntax, return values, limits, lifecycle and error contracts, and the rationale for locally surprising behavior. This log retains shared architecture, accepted historical choices, superseded alternatives, and unresolved discrepancies. README/tutorials retain usage; release procedures remain in the release checklist and workflow documentation.
Decorated command docstrings are also generated user help, so keep developer implementation details on framework objects or tutorial module docstrings. Test documentation explains fixture scope and regression intent without repeating self-explanatory test names. Reconcile session suggestions against explicit approval and current code; do not turn proposed features into promises.
Accepted follow-up, September 21: give user-facing positional arguments brief
completion/help descriptions once tutorial 04 has introduced Argument metadata.
Keep examples 01–03 focused on their earlier concepts instead of using argument
configuration before the tutorial explains it.
Accepted: plain hex ignores whitespace anywhere between digits, with an even total digit count. When any whitespace-separated component has a 0x/0b/0o prefix, parse each component as a single byte; bare components are decimal. Binary underscores follow Python placement rules. Reject malformed syntax and values outside 0..255. Preserve standalone contiguous 0x multi-byte input and existing separate byte-separator and escape formats. Command input may use either quote style. Fuzzy pattern parsing is unchanged.
The implicit decimal rule above is superseded by explicit mixed-radix byte patterns below.
Accepted: convert expected user-facing failures at command boundaries to CommandError. Preserve ordinary exceptions for reusable Python APIs and programmer mistakes. Interactive execution, completion, and shortcut boundaries contain unexpected exceptions and show diagnostics without reclassifying cancellation or process-exit signals as command failures.
Accepted follow-up, September 21: when failed input is restored, keep precise validation-error positions at the offending argument. For a CommandError or unexpected exception without a position, place the cursor at the end of the restored command so editing can resume from a predictable location.
Accepted: ignore whitespace between plain FuzzyHexPattern elements, including nibbles, complete classes, and repetition expressions. Reject whitespace inside classes or repetition counts and require complete bytes after normalization. Keep prefixed/escaped and colon/hyphen/underscore formats unchanged. Do not add mixed-radix fuzzy syntax. FuzzyStringPattern preserves literal whitespace. Example 06 displays expanded and sampled hex without separators.
The earlier exclusion of mixed-radix fuzzy syntax is superseded by the next decision; plain-pattern whitespace behavior remains.
Accepted: both HexBytes and mixed FuzzyHexPattern require 0x/0b/0o/0d prefixes on every component. This supersedes implicit bare decimal bytes. Fuzzy binary/octal components support radix-specific digits, wildcards, classes, and digit repetition. Underscores separate atoms or follow prefixes, never consecutive/trailing or inside constructs. Decimal 0d sets reuse IntegerRanges syntax, sort and deduplicate choices, and validate bounds before expansion. Every possible component value must fit 0..255; reject rather than filter. Plain fuzzy hex and standalone contiguous 0x multi-byte notation remain.
Accepted scope, September 19: move reusable operating preferences to
~/.codex/AGENTS.md and keep repository instructions specific to ctui, balancing
quality with context cost. The global file is machine-local and must be copied
separately when moving laptops; it does not travel with this repository.
Implementation choice: keep STATUS as a current snapshot with dated validation and open work; retain detailed chronology in Git. Read decision headings and relevant entries per task, broadening for architectural work. Preserve decision rationale and supersession history instead of compressing away API constraints. This refines D11's instruction placement and startup reading, not its requirement for durable project continuity.
Frontend design recorded October 1 during development on feat/web-frontend.
The optional-dependency choice was made without owner approval and is superseded
by the owner's October 5 direction: aiohttp is a required dependency so a standard
installation supports browser mode without extra installation flags.
One CtuiApp definition supplies three mutually exclusive startup modes: no
arguments opens the existing terminal UI, -c/--command and -f/--file run CLI
operations, and --web starts only the browser frontend. Print a connection URL
and keep the process running. A process owns one live session; separate
processes/ports own separate sessions. Multiple tabs at the same address share
app state/output while drafts, focus, scrolling and dialogs stay per-view.
Owner direction, October 5: typed exit requires confirmation in both interactive
frontends. On approval the shared session stops; the submitting browser tab
attempts to close, with a manual-close message if browser policy prevents it.
Existing explicit confirmation tokens remain available to noninteractive callers.
Submitted commands continue after their tab disconnects; unanswered confirmation or input dialogs cancel on disconnect, preserving explicit approval.
Use packaged HTML/CSS and small JavaScript modules/functions with no frontend framework or build step. aiohttp is a required dependency for HTTP/1.1 and WebSockets on the existing asyncio loop. Loopback HTTP is the default; explicit remote binds require supplied TLS certificate/key files and authenticated access. HTTPS uses secure WebSockets. Automatic certificate issuance is deferred.
Preserve the existing supported compose() API using a browser adapter over its prompt-toolkit tree. Compatible Button/Frame/ProgressBar subclasses retain widget identity through upstream container conversion. This avoids forcing existing apps to define a second layout or migrating the supported terminal API. An independent declarative widget model is not introduced in this change. Arbitrary third-party controls and custom floats require future adapters and fail explicitly. Implementation/lifecycle, queue bounds, session credentials, and limitations live in the application/web/layout/dialog docstrings and README. Help remains a per-view scrollable popup that preserves the main output.