| D-M8-08-0 |
2026-10-23 |
Built on feat/M8-08-import-console from origin/main (M6-04 and M8-03 merged); rulings and changelog are fragments. Names that differ from the brief: the screens live in packages/admin_ui/lib/src/import/ (ImportRepository as part of AdminRepository, ImportController, ImportListPage, ImportSourcePage, ImportCreateDialog, ImportChannelTable, ImportConfirmDialog, ImportOperatorDialog); the place is AdminSection.dataImport (web only, so AdminSection.listed and the desktop shell leave it out), and the console hosts it through its existing ConsoleRoute and ConsoleAdminPage (/import, /import/<id>, the mapping picker in manifest_target.dart) instead of a new apps/console/lib/admin/import/ directory. M4-28 has no channel operator dialog, so the operator step is ImportOperatorDialog over setChannelOperator with the guided switch of M4-28 (GuidedSwitch, GuidedFlow, GuidedResult). The seed persons are Alex (owner) and the agents Support Agent and Texter, so the patrol flow plans Texter instead of Lotse and runs as Alex instead of Robin; the suite file is import_test.dart. |
Instructions of the launcher; merged names win (D-W-100). |
| D-M8-08-1 |
2026-10-23 |
Refresh without polling: ImportController keeps the newest ImportSource per ID; every snapshot replaces the shown one, also one with lower counters, because the server is the source. A snapshot comes from getImport after each step (also a refused one), from the refresh control and from AppLifecycleListener.onShow, which the web engine raises on visibilitychange to visible; applyProgress takes the payload of import.progress for a client with a gateway. There is no timer, as no configuration key sets an interval and the console has no gateway (15 3.1 rule 3). The channel rows join the mapping with the posted channel items, which listImportItems lists over every page because the operation has no filter by item kind. |
Brief Design note 1; fixed requirement 16. |
| D-M8-08-2 |
2026-10-23 |
Mapping file in the browser: the YAML is parsed with package:yaml, which refuses a key named twice; the document must be a mapping with text keys, else the dialog names the problem with line and column; the result is sent unchanged as the JSON of config, so the server's closed schema checks every member. A 400 validation_failed lists every error with its pointer below /config translated to the place in the file (/config/channels/2/planned_operator is channels[2].planned_operator) and its code in plain language; the text never reaches browser storage. An unquoted Discord ID stays a number and is reported by the server at its pointer. |
Brief Design note 2; 22 5.2. |
| D-M8-08-3 |
2026-10-23 |
Lead steps: a row offers them only while its channel item is posted and the source is neither completed nor failed. "Lead here" confirms with the order of 22 13.3 (Discord read-only and agent removed, bothy import discord switch --channel <id> --discord-locked succeeded, then the lead, then the planned operator) and warns when the channel already has another operator; after setImportLead bothy the operator dialog opens with the planned agent (when active) and a required reason, warns when another agent is chosen (R-IM-08) and on 409 i1_violation offers the guided switch in one request with resolve_i1; closing it leaves the row "Led here, no operator" with "Set operator". "Discord leads" confirms read-only for everyone, answers in progress cancelled and the revert command, then offers removing the operator (mode none). The operator check compares the channel's effective operator with the planned agent found by handle. |
Brief Design note 3; 22 12.1, 13.3, 13.4. |
| D-M8-08-4 |
2026-10-23 |
State buttons: draft start and cancel, running pause, cancel, complete, paused start, cancel, complete, completed and failed none; complete only while every imported channel has lead = bothy, with the retention warning of 22 13.5 in its confirmation; cancel is destructive. The 409 conflict reasons import_state, lead_not_bothy and channel_not_imported read in plain language and the page then shows the server's state. A failed item shows its kind, origin key and an error only when it is a code, so no content of the source reaches the screen; counters are the numbers of stats only. |
Brief Design note 4; fixed requirements 12 and 15. |
| D-M8-08-5 |
2026-10-23 |
The interface texts and the client code never spell the product's working name (ADR 0044; tools/ci/tests/test_product_name_places.py): the brief's "Bothy leads" reads "Lead here" and its rows "Led here, ...", the lead of this server is ImportLead.here, its wire value is taken from the generated ImportLeadSetLeadEnum (the one value that is not discord), and the reason of a completion refused for a channel not led here is matched by its prefix lead_not_. The commands of the import tool keep their binary name (bothy import discord ...), which 02 6 allows. |
ADR 0044; 02 6. |