Commit 287c2b6
* feat(models): add ConnectionAppearance and IconOverride (#189)
* fix(commands): preserve appearance on update_connection + complete serde roundtrips (#189)
* feat(types): mirror ConnectionAppearance / IconOverride in TS (#189)
* feat(connections): add save/delete_connection_icon tauri commands (#189)
* fix(icon-validation): close SVG scanner bypasses + tmp cleanup (#189)
* feat(tauri): register appearance commands + fs capability (#189)
* feat(connections): cascade-delete custom icon with connection (#189)
* feat(ui): add getConnectionAccent/getConnectionIcon resolvers (#189)
* fix(ui): tree-shake icon pack + ConnectionIconImage state regressions (#189)
- Extract curated 30-icon subset into connectionIconPack.ts (src-of-truth for picker + resolver)
- Replace import * as LucideIcons with named pack import to enable tree-shaking
- Reset failed state on path change so ConnectionIconImage recovers from prior failures
- Guard onError against post-unmount setState via mountedRef
- Add aria-hidden to emoji span; narrow getConnectionIcon Pick to "appearance" only
- Add ConnectionIconImage.test.tsx with regression test for sticky-failed fix
- Update global lucide-react mock in tests/setup.ts with pack icons
* feat(ui): route 4 connection views through new resolvers (#189)
Replace getDriverColor/getDriverIcon with getConnectionAccent/getConnectionIcon
in OpenConnectionItem, ConnectionCard, ConnectionListItem, and VisualExplainModal
so per-connection accent colours and icon overrides are honoured everywhere.
* feat(ui): AppearanceSection color picker (#189)
* fix(ui): sync displayed hex input to normalized value (#189)
* feat(ui): AppearanceSection icon picker tabs (default/pack/emoji/image) (#189)
* fix(ui): AppearanceSection race + empty-emoji UX + remove a11y (#189)
* feat(ui): mount AppearanceSection in NewConnectionModal (#189)
Add set_connection_appearance Rust command to patch the stored appearance
field after save. Mount AppearanceSection at the bottom of the General tab,
wired with appearance state pre-populated from initialConnection on edit.
For new connections a stable UUID is generated on mount and used as the icon
upload target; after save the appearance (with its icon path) is persisted
under the real backend-minted id via set_connection_appearance.
* fix(ui): clean up orphan icon files + tests for set_connection_appearance (#189)
- Delete previous image when user picks a new one in AppearanceSection (intra-session orphan)
- Delete uploaded-but-unsaved image on modal cancel via useEffect cleanup + wasSavedRef
- Extract set_appearance_impl pure helper; add 3 unit tests (update, clear, missing-id)
* i18n: add connectionAppearance strings to all locales (#189)
* fix(ui): sync custom-color draft + remove unused i18n keys (#189)
When the Custom hex input is opened after a palette swatch was picked,
the draft now mirrors the current accentColor so closing the field can't
silently revert it. Also drops i18n keys for errors that aren't wired up
yet (tooLarge, invalidFormat) and the preview label that has no UI.
* feat(ui): integrate react-colorful for full color picker (#189)
Replace the manual hex text input and regex validation in AppearanceSection
with HexColorPicker (saturation/hue picker) + HexColorInput (lib-validated
hex input), keeping the 12-swatch curated palette intact. Removes HEX_RE,
customDraft, hexError, and commitCustom (~30 lines). Updates tests to cover
the new panel open/close and live-emit behaviour.
* test(ui): make hex-input filter test meaningful instead of tautological (#189)
* fix(image-upload): enable asset protocol scope for connection-icons directory
The root cause of image upload silently breaking at display time: Tauri 2's
assetProtocol was disabled (default), so convertFileSrc() produced URLs the
webview refused to load. Fix:
- Enable assetProtocol in tauri.conf.json, scoped to $APPDATA/connection-icons/**
- Add fs:scope-appdata-recursive to capabilities so appDataDir() resolves
- Add DEV-only console.error in ConnectionIconImage.onError to surface future failures
* feat(ui): overhaul AppearanceSection UX — tab sync, preview, icon search, frimousse, image preview (#189)
- Derive active tab reactively from value.icon.type (approach B: userTab ?? derivedTab)
so reopening an edited connection shows the correct tab without stale useState
- Add preview row at top showing current accent color dot + connection icon + name;
threads driverManifest and connectionName props from NewConnectionModal
- Add search input above icon pack grid (case-insensitive id filter)
- Replace plain emoji <input> with Frimousse headless picker (built-in search,
only emits valid single emoji) — removes graphemeCount, emojiDraft, emojiError,
commitEmoji dead code
- Show ConnectionIconImage thumbnail in Image tab after upload or when editing
an existing image connection
- Split pickImage() error handling into two distinct try/catch blocks with
console.error logging so failures point to the exact failing step
* test(appearance): update tests for Frimousse picker, icon search, and tab sync
- Remove old emoji text-input tests (blur/validation no longer apply)
- Add Frimousse mock to avoid async emoji data fetching in JSDOM
- Add: emoji picker renders with search input, onEmojiSelect emits correctly
- Add: pack icon search filters results by search term
- Add: tab syncs to value.icon.type on external prop change (edit mode)
* fix(ui): userTab stuck-tab regression + protocol-asset cargo feature (#189)
Tab state used to stay on the user's last click even after value.icon
changed externally (parent re-render with new initialConnection). Resets
userTab to null when value.icon.type differs from the previous render.
Also enables the protocol-asset Cargo feature on tauri — required at the
crate level for assetProtocol to actually serve files via tauri:// URLs.
Without it, assetProtocol in tauri.conf.json is a no-op.
Minor: memoize filteredPackIcons, route "No preview" through i18n.
* feat(ui): swap to emoji-picker-react + open icon pack to all lucide icons (#189)
- Replace Frimousse with emoji-picker-react (dark theme, categories, skin tone selector, recent emojis, preview disabled)
- Expand icon picker from 30 curated icons to all ~1900 lucide icons via dynamicIconImports with lazy loading + 120-result display cap
- Add camelCase→kebab-case normalization for legacy stored icon ids in both resolver and picker
- Wrap lazy icon components in Suspense in driverUI and AppearanceSection pack grid
- Update test mocks: frimousse → emoji-picker-react, dynamicIconImports proxy in setup.ts
- Add camelCase normalization tests in driverUI.test.tsx
* fix(security): path-traversal guard in cascade_delete_if_image + copy_icon_for_duplicate (#189)
- cascade_delete_if_image: canonicalize both paths and reject any file
not under <app_data>/connection-icons/ before deletion
- Add copy_icon_for_duplicate helper that copies icon files with the
same path-containment guard, so duplicate_connection gives each
duplicate its own icon file rather than sharing the original path
- Wire copy_icon_for_duplicate into duplicate_connection in commands.rs;
falls back to dropping the icon on copy failure instead of sharing
- Add regression tests: cascade_delete_rejects_path_traversal,
copy_icon_for_duplicate_produces_new_filename,
copy_icon_for_duplicate_rejects_path_traversal
* fix(ui): defer icon-file deletion until connection save (#189)
Root cause of user data-loss bug: AppearanceSection eagerly called
delete_connection_icon when the user picked a replacement image, so if
the session was later cancelled the "previous" file was already gone but
connections.json still referenced it.
- AppearanceSection: remove eager delete_connection_icon from pickImage
and removeImage; add onImageUploaded prop so the parent can track uploads
- NewConnectionModal: track all uploaded paths in uploadedPathsRef
- On cancel: delete every session upload except the original image
(handles "pick A → B → C → cancel" correctly)
- On save: delete every uploaded path except the final one, plus the
original if the user replaced it
- Add test: "does not eagerly delete previous image on pick"
* fix(ui): proxy symbol guard + explicit HexColorInput prefixed (#189)
- CONNECTION_ICON_PACK proxy: guard get trap with typeof key !== "string"
so Symbol accesses (Symbol.toStringTag, Symbol.iterator, etc.) from
Object.prototype.toString.call(), React DevTools, etc. return undefined
instead of throwing TypeError in camelToKebab's .replace() call
- HexColorInput: add explicit prefixed={false} to future-proof against
react-colorful changing its default
- Add tests: Symbol.toStringTag and Symbol.iterator proxy access
* docs(readme): per-connection appearance + connection-icons/ storage (#189)
* chore(types,lint): drop any usage, fix set-state-in-effect, jest-dom vitest matchers (#189)
- connectionIconPack: typed IconLoader instead of `any` for dynamicIconImports loader
- ConnectionIconImage: reset src/failed via render-phase comparison (avoids react-hooks/set-state-in-effect lint warning) and the matching test still passes
- driverUI.test/AppearanceSection.test: remove `any` casts and unused locals
- tests/setup.ts: switch to `@testing-library/jest-dom/vitest` so the type augmentation applies under TSC
- tsconfig.app.json: include tests/setup.ts so the augmentation is picked up
No behavior changes; TSC and ESLint now run clean.
* feat(ui): move Appearance to its own tab in NewConnectionModal (#189)
Previously the AppearanceSection lived inline at the bottom of the General
tab. Promote it to a peer tab alongside SSL/SSH so per-connection icon and
accent color get the same first-class treatment as other settings groups.
- new "appearance" entry in the activeTab union and tab bar (always shown,
no driver restriction)
- pull the AppearanceSection JSX out of generalTabContent into its own
appearanceTabContent
- add newConnection.appearance i18n key across all 7 locales
* style(ui): polish Appearance tab to match modal design system (#189)
Align styling with the General tab and improve visual hierarchy:
- preview row with accent gradient + status label
- color swatches with Check on selected + custom button with Pipette icon
- icon tabs gain identifying icons (Sparkles/Grid3x3/Smile/Image)
- Default tab click is now the reset action (one-click revert)
- pack icon grid in a bordered surface with cleaner selected state
- emoji picker themed via --epr-* CSS variables to match dark theme,
plus a selected-emoji status card with Clear button
- image tab redesigned as a thumbnail + actions card
---------
Co-authored-by: Andrea Debernardi <andrea@debbaweb.it>
1 parent bbf73c6 commit 287c2b6
43 files changed
Lines changed: 2164 additions & 20 deletions
File tree
- src-tauri
- capabilities
- src
- src
- components
- connections
- layout/sidebar
- modals
- NewConnectionModal
- contexts
- i18n/locales
- utils
- tests
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
| 166 | + | |
165 | 167 | | |
166 | 168 | | |
167 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
| 202 | + | |
202 | 203 | | |
203 | 204 | | |
204 | 205 | | |
| |||
315 | 316 | | |
316 | 317 | | |
317 | 318 | | |
| 319 | + | |
318 | 320 | | |
319 | 321 | | |
320 | 322 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
94 | 95 | | |
95 | 96 | | |
96 | 97 | | |
| |||
161 | 162 | | |
162 | 163 | | |
163 | 164 | | |
| 165 | + | |
164 | 166 | | |
165 | 167 | | |
166 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
46 | 47 | | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| 54 | + | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments