hotfix(setup): UI mode now re-packs .gtbundle after successful save#92
Merged
Conversation
`run_simple_setup` calls `gtbundle::create_gtbundle` after `engine.execute` to write the configured bundle back to its `.gtbundle` archive. `run_ui_mode` did the same engine work — answers, secrets, tenant config all written under `bundle_dir` — but **never re-packed** the extracted dir back to the user's input file. Result: every `gtc setup` (default UI mode) on a `.gtbundle` left the original archive untouched, and the next `gtc start` saw none of the just-saved secrets and panicked with `secret error: not-found`. Fix: - Add `output_target: Option<SetupOutputTarget>` to `UiState` and the `ui::launch` signature so the UI flow knows whether the user passed a `.gtbundle` archive (re-pack) or a directory (copy). - `run_ui_mode` now computes `setup_output_target(&bundle_path)` up front (same helper the simple-mode flow uses) and passes it through. - The `/api/execute` UI handler runs the existing `execute_setup` blocking task; on `result.success` it dispatches a follow-up `spawn_blocking` that calls `gtbundle::create_gtbundle` (Archive) or `copy_dir_recursive` (Directory) and appends a "Configured bundle written to: …" line to the result. Re-pack failure flips `success` to false and surfaces the error in `stderr` so the UI can show it instead of silently lying about success. - Derive `Clone` on `SetupOutputTarget` since the value is now stored in `UiState` (`Arc`-shared) and consumed inside the spawn_blocking closure. Verified: a `.gtbundle` run through `gtc setup` now contains the populated `state/config/<provider>/setup-answers.json` and `.greentic/dev/.dev.secrets.env` after extraction, where it was empty before this change.
…wers Phases 2–3 of the table-kind RFC (greenticai/greentic-types#112). Wizard operators can now answer repeating-row questions row-by-row instead of typing JSON arrays into a single textarea. Changes: - `setup_input::SetupQuestion` accepts `kind: table` plus `columns: [...]`, `min_rows`, `max_rows`. New `SetupTableColumn` struct mirrors the per-row schema (key/title/kind/required/help/placeholder/choices/default). - `setup_to_formspec::convert::convert_table_question` bridges the setup.yaml shape into qa-spec's existing `QuestionType::List` + `ListSpec.fields`. Each column becomes a nested `QuestionSpec` keyed by its `key` field. Nested tables are not supported — column kind collapses to scalar. - `qa::prompts::ask_list_question` implements the CLI prompt loop: "Add a row? [y/N]", per-column prompts, drop rows with empty required columns, enforce `min_items`/`max_items`. Returns a `Value::Array` of per-row JSON objects. - `tenant_config::sync_nav_links_to_tenant_config` now accepts the native array shape (`nav_links`) produced by the table wizard, in addition to the existing `nav_links_json` string fallback. New helper `sanitize_nav_link_array` deduplicates the validation logic and adds i18n-keyed label support (locale-keyed object passes through). Tests: 23 tenant_config (1 new — table-shape answer) + 22 setup_to_formspec (1 new — table → list bridge). 223 lib total. Phase 4 (canary migration of `nav_links_json` to Table kind in messaging-webchat-gui setup.yaml) lands in greenticai/greentic-messaging-providers PR #149 alongside this.
Carries the four PRs in the setup-roundtrip family up onto a stable release tag so `cargo install greentic-setup` (and therefore the `gtc setup` router target) picks up the fixes: - #89/#90: squashfs perms (re-packed bundle now extracts as 0o755/0644 instead of 0o000), `--non-interactive` no longer hangs on tunnel prompt, `emit-answers` template now includes `platform_setup.tunnel`. - #91/#92: UI mode (default `gtc setup`) now re-packs the .gtbundle after engine.execute — answers + secrets + tenant config actually land inside the squashfs instead of being dropped on temp-dir cleanup. Tag `v0.5.8` after merge to trigger crates.io publish via release-binaries.yml.
Phase 5 of the table-kind RFC. The web wizard now renders QuestionType::List
questions as a real table with one input per column, an Add row button,
and trash-icon row removal — instead of falling through to the empty
Back button stub.
Wire-up:
- QuestionInfo gains list_columns plus min_rows/max_rows.
- app.js: renderQuestion handles kind==='List', appendTableRow builds
one tr of inputs, setupTableQuestions wires the Add button and seeds
saved rows. restoreFormValues skips List, collectFormValues walks
tbody rows and builds per-row JSON objects.
- style.css: minimal table styling.
End-to-end: operator sees a real table at gtc setup --ui, fills per-row,
submit posts { nav_links: [{label, url, external?}, ...] }, sync function
writes the array to tenants/<tenant>.json, SPA renderTopbarNav consumes.
…PPORTED_LOCALES set
- Replace per-cell "+ language" pickers with a single row-level toolbar
that adds locale sub-rows to every multilingual cell at once and
removes them symmetrically via chip ✕. Saves four clicks per row and
keeps the pre-mount layout from cramming pickers into 7 columns.
- Pre-populate the nav_links table on wizard re-run by reading the
bundle's persisted tenant.json::nav_links via a new
read_existing_nav_links helper that flattens the on-disk nested
tooltip {eyebrow,title,lede} shape back to the wizard's flat columns.
saved_rows is sent through the new QuestionInfo field.
- Migrate legacy nav_links_json string answers to nav_links arrays in
/api/scopes prefill, then drop the legacy key. Without this the
ghost overrides the table edits the user just made on next sync.
- Fix collectFormValues silently dropping URL/Boolean/scalar columns:
td.dataset.col (added for CSS column-width targeting) made the bare
[data-col] selector match the parent <td> first; cell.value read
back undefined and required-column rows got filtered out, leaving
nav_links absent from the answers payload. Selector now scopes to
input/select.
- Switch form-area listeners to event delegation so dynamically-added
locale-row inputs participate in autosave.
- Per-column min-widths + container widening to 1380px for the
7-column nav_links table; row-table__lang-row chips match brand.
- Verbose [hydrate] / [nav_links] logging for round-trip diagnosis.
- Bump 0.5.8 → 0.5.9.
CI fmt found two long lines in tenant_config.rs / ui/mod.rs; clippy flagged the migration loop as single_element_loop. Open-code the single legacy_key migration with const strings — when we add more `_json` legacy answer shapes we can re-introduce the loop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Forward-port of #91 (merged into `develop`) into `main` so the next stable cut carries the fix without waiting for the develop → main promote.
What it fixes
`gtc setup` defaults to UI mode for `.gtbundle` inputs, but `run_ui_mode` never called `gtbundle::create_gtbundle` after the engine finished — answers/secrets/tenant config were written to the extracted temp dir and dropped on exit, leaving the original `.gtbundle` archive untouched. Result: `gtc start` saw none of the just-saved secrets and panicked with `secret error: not-found`.
See PR #91 for full breakdown — tl;dr: hook `gtbundle::create_gtbundle` (Archive) / `copy_dir_recursive` (Directory) into the `/api/execute` UI handler when `execute_setup` returns `success: true`.
Verification
Related