Skip to content

Platform overhaul: Quarkus/Vue modernization, Keycloak auth, shadcn-vue migration, Azure decoupling#1

Open
dominikhorn93 wants to merge 37 commits into
developfrom
claude/platform-overhaul
Open

Platform overhaul: Quarkus/Vue modernization, Keycloak auth, shadcn-vue migration, Azure decoupling#1
dominikhorn93 wants to merge 37 commits into
developfrom
claude/platform-overhaul

Conversation

@dominikhorn93

Copy link
Copy Markdown
Member

Large, end-to-end overhaul of ProA developed over several autonomous sessions. It is one PR by request; it is best reviewed commit by commit (37 atomic, individually-green commits). Architecture decisions are recorded under docs/adr/.

What's in here

Toolchain modernization

  • Backend: Quarkus 3.8.3 → 3.27.4 LTS (the project did not compile on JDK 25 before), Lombok via annotationProcessorPaths, JaCoCo 0.8.15.
  • Frontend: Vue 3.3→3.5, Vite 4→7, TypeScript 5.9, ESLint 8 → ESLint 9 flat config, Vuetify→shadcn (see below), axios CVE fixes; all versions pinned exactly.

Guardrails

  • Git hooks that actually fail (the old ones never exit 1 and core.hooksPath was never set): fast pre-commit (lint/format), pre-push (affected test suites).
  • New Frontend Checks CI workflow (Prettier/ESLint/type-check/build); Backend Tests triggers on root pom.xml too; Dependabot for Maven/npm/Actions.

Local dev setup

  • make setup + make backend/backend-pg/frontend/auth-up; docker-compose with PostgreSQL 17 (+fuzzystrmatch) and Keycloak; .env.example; rewritten README.

Security & authentication — ADR-0001 (Keycloak/OIDC)

  • The entire homegrown identity stack (JWT issuing, login lockout, rate limiting, key generation, admin seeding) is deleted and replaced by Keycloak via quarkus-oidc (backend) + oidc-client-ts PKCE (frontend). Authorization stays in the app.
  • Fixed an IDOR (process endpoints didn't check project membership), XSS (v-html), XXE (BPMN parsing hardening), fail-open app.mode default (now fail-closed), and a missing JWT aud claim. Identity is keyed on the immutable OIDC subject (email-recycling takeover prevented).

Project invitations — ADR-0003

  • In-app user management removed (identity is Keycloak's domain); owners invite by email, pending invitations are redeemed into memberships on the invitee's first verified login.

Gemini removed

  • The client-side @google/generative-ai feature (API key reached the browser) is removed entirely; it returns later as an MCP-backed, server-side feature.

Deployment decoupled from Azure

  • Datasource is fully env-driven (no Azure host hardcoded); the deploy workflow publishes a container image to GHCR. Runtime target is decoupled (supply QUARKUS_DATASOURCE_* and QUARKUS_OIDC_AUTH_SERVER_URL).

Vuetify → shadcn-vue (complete)

  • The whole UI migrated to shadcn-vue (Reka UI + Tailwind v4); Vuetify and its deps removed, global snackbar → vue-sonner, Tailwind preflight enabled. Only the JointJS process-map canvas remains, intentionally deferred to the diagram-js extraction (ADR-0002).
  • Fixed the process map along the way: a dangling-link guard before the directed-graph layout (graphlib phantom-node crash) and a viewport-based canvas height (a percentage-height regression from the new flex shell).

Verification

  • Backend: ./mvnw verify — 372 tests green.
  • Frontend: yarn lint:check / format:check / build — green.
  • Manual E2E (Playwright against Keycloak + Quarkus + Vite): Keycloak login, project CRUD, settings, BPMN upload, the process-map node rendering, the invitation flow.
  • Each major phase was run through a multi-agent adversarial review; confirmed findings were fixed (incl. an unstyled-toast regression the happy-path E2E missed).

⚠️ Before this goes to production

  • Rotate the JWT keypairrsaPrivateKey.pem is in git history and old jars (the runtime no longer uses it after ADR-0001, but treat it as compromised if ever deployed).
  • Provision Keycloak: host it, set QUARKUS_OIDC_AUTH_SERVER_URL + the VITE_OIDC_* repo variables, register deployed origins as redirect URIs.
  • Use a separate database/login per environment (include sslmode=require in the prod JDBC URL).
  • Camunda connection secrets are still stored plaintext in the settings table.

Deferred follow-ups

  • ADR-0002: replace the JointJS process-map canvas with diagram-js (consolidates on one diagram stack; also fixes the single-node auto-fit nit).
  • Flyway baseline (prod still uses hibernate generation=update), vue-i18n legacy→composition API, Pinia god-store split, optional shadcn.css canonicalization / z-[2400] revert (see docs/UI-MIGRATION.md).

🤖 Generated with Claude Code

dominikhorn93 and others added 30 commits June 11, 2026 00:00
- Quarkus 3.27.4 (JDK 25 compatible, security fixes, 2 years of updates)
- Rename quarkus-resteasy-reactive* to quarkus-rest* (3.9+ artifact names)
- Lombok 1.18.46 via annotationProcessorPaths (required since JDK 23
  disabled implicit classpath annotation processing)
- JaCoCo 0.8.15, quarkus-bucket4j 1.0.7
- Extract CamundaOperateServiceFactory from CamundaCloudImportUsecase:
  usecase no longer builds HTTP clients itself, tests mock the factory
  instead of spying on the usecase

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Vue 3.3 -> 3.5, Vite 4 -> 7, TypeScript 5.2 -> 5.9, vue-tsc 1 -> 3
- Vuetify 3.3 -> 3.12, bpmn-js 14 -> 18, axios 1.5 -> 1.17 (CVE fixes)
- Pinia 2 -> 3, pinia-plugin-persistedstate 3 -> 4 (persist option
  renamed paths -> pick; drop nonexistent activeProjectByGroup entry)
- vue-i18n 9 (EOL) -> 11, vue-router 4.2 -> 4.6
- ESLint 8 legacy config -> ESLint 9 flat config with
  vue3-recommended + TS recommended + prettier skip-formatting
- moduleResolution: bundler; pin all versions exactly (project rule)
- Drop unused deps: core-js, @babel/types, @types/cytoscape
- New scripts: type-check, lint:check, format, format:check

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docker-compose.yml: PostgreSQL 17 with fuzzystrmatch extension
  auto-enabled via scripts/init-db.sql (host port 5433 to avoid
  clashes with other local databases)
- Makefile: setup, db-up/down/reset, backend(-pg), frontend, test,
  lint, format, build targets
- dev-postgres profile now matches the compose service and keeps
  data between restarts (update instead of drop-and-create)
- Remove committed rsaPrivateKey.pem from the index and ignore
  intermediate key files (NOTE: rotate keys if this pair was ever
  used in production - it remains in git history)
- Prod profile: disable SQL logging; migrate deprecated
  quarkus.package.type to quarkus.package.jar.type
- frontend/.env.example documents VITE_APP_MODE

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New frontend-checks workflow: Prettier, ESLint, type-checked build
  on every PR touching frontend/
- backend-tests: also trigger on root pom.xml changes; add concurrency
  group to cancel superseded runs
- Rewrite git hooks: failures now actually abort (the old hooks only
  echoed and continued); pre-commit runs fast lint/format checks on
  staged files only, pre-push runs the affected test suites
- Dependabot for Maven, npm and GitHub Actions
- frontend-maven-plugin: Node 20 (EOL) -> 22 LTS, yarn 1.22.22

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mp.jwt.verify.audience=proa-client is configured, but TokenService
never set an aud claim on issued tokens, so signer and verifier
disagreed. Add .audience("proa-client") and assert it in the test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- XSS fix: ProjectOverview delete-confirmation rendered user-controlled
  project/version names via v-html; now uses <i18n-t> slots so names
  are escaped text nodes
- Declare emits in all components (40 occurrences)
- Fix v-for keys, prop mutation (dialog v-model -> computed + close
  event), required-prop defaults, component name casing
- Replace any-casts with precise types; remove dead vars/params
- Convert setMode.js to ESM (setMode.mjs)
- Mechanical Prettier/ESLint --fix formatting across all sources

yarn lint:check, format:check and build are all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pre-push used 'eslint --ignore-path', which ESLint 9 flat config
  rejects - every frontend push would have been falsely blocked.
  Use 'yarn lint:check' instead
- New 'desktop' profile with file-backed H2 (~/.proa) so released
  desktop jars actually persist data across restarts
- default-schema=${STAGE:local} so the jar can start without STAGE

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Tailwind v4 via @tailwindcss/vite; preflight intentionally NOT
  imported yet so Vuetify keeps working during the strangler-fig
  migration (see comment in src/styles/shadcn.css)
- shadcn theme tokens (oklch) with primary mapped to the existing
  brand blue (#1867C0 equivalent); dark-mode variables prepared
- components.json + cn() util; 19 ui components generated via CLI:
  alert avatar badge button card checkbox dialog dropdown-menu input
  label select separator sheet skeleton switch table tabs textarea
  tooltip
- ESLint override for generated src/components/ui/**
- All new deps pinned exactly (project rule)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Any authenticated web-mode user could read, delete or import into
other users' projects by guessing ids - process-model, process-map
and Camunda-import endpoints never checked project membership.

- ProjectAccessService (usecases) + ProjectAccessRepository port +
  ProjectAccessDao impl: resolve resource id (process model,
  connection, project version) -> owning project -> membership
  (OWNER/COLLABORATEUR) of the JWT user
- ProjectAccessVerifier (rest) applies the check in web mode before
  every usecase call; 403 for non-members, 404 for unknown ids;
  desktop mode unchanged
- Verified id semantics: outside ProjectResource, the 'projectId'
  path segment is a ProjectVersionTable id (documented in
  ProjectAccessService)
- 59 new tests incl. cross-user 403/404 cases; suite: 324 green
- Test profile pins default-schema=PUBLIC (H2) after STAGE:local
  default broke native cleanup queries

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
API layer (src/api): single axios client with baseURL /api, auth
header injection from the store, and 401 interceptor (web mode:
clear session + redirect to sign-in; login uses skipAuth). Typed
feature modules auth/users/projects/processModels/processMap/
settings/camundaCloud replace raw axios calls copy-pasted across
17 components; authHeader.ts and ad-hoc services deleted. Shared
types moved out of .vue files into src/types (kills circular
imports).

Bug fixes:
- ProcessMap view state (graph, paper layout, filters, hidden
  cells/ports/links) now saved AND loaded under the project version
  id - restore was silently broken by mismatched keys
- Active instance counter counted every non-ACTIVE instance as 1
- Router guards: no more window.history.back(); proper vue-router 4
  return values, direct-entry redirects to ProjectOverview
- Global snackbar moved to the default layout (was Home-only)
- v-model-on-setterless-computed fixed in AuthenticationDialog and
  EditUserDialog; lastName input no longer type=email
- SettingsDrawer: VITE_OPERATE_REGION_ID name fixed, save errors
  surfaced; Gemini failures no longer lock the upload dialog
- Sort comparator and i18n gaps (5 components, en/de) fixed

yarn lint:check, format:check, build: green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- XXE: secure DocumentBuilderFactory/TransformerFactory settings in
  BpmnOperations (DOCTYPE disallowed, external entities/DTD/stylesheet
  access off); DOCTYPE pre-check on BPMN uploads returns a clear 400;
  test proves external-entity BPMN is rejected without entity
  resolution (camunda-xml-model parser verified DTD-safe)
- app.mode now defaults to web everywhere: a missing property fails
  CLOSED (auth on) instead of silently disabling authorization
- New WebModeAuthorizationTest exercises 401/403/200 with real signed
  JWTs - role checks were previously never tested (tests ran desktop)
- Bean Validation: registration validates email format and password
  length (validation group keeps seeded admin login working)
- FileService no longer swallows IOException (uploads can't be
  silently stored empty); NPE fixes in ProjectRepositoryImpl
  (unknown project -> NoResultException) and SettingsRepositoryImpl
  (create-on-first-write)
- AdminInitializer warns loudly when default admin credentials are
  used in web mode; printStackTrace replaced with quarkus Log

Tests: 346 green (+22).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gration docs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First strangler-fig wave (see docs/UI-MIGRATION.md):
- SignIn: Card composition, Input/Label with aria-invalid errors,
  destructive Alert, loading state on submit (prevents double login)
- AuthenticationDialog + CreateAccount/ChangePassword/ProfileDialog/
  EditProfileDialog: shadcn Dialog driven by the existing
  SelectedDialog store mechanism; persistent semantics preserved
- ManageUsers/EditUserDialog: shadcn Table, Badge roles, ghost icon
  actions with Tooltip, destructive delete button
- Validation logic unchanged (firstRuleError helper runs the same
  rule arrays); store calls, emits, i18n keys and API calls intact
- mdi icons -> lucide; authentication.css removed
- Button.vue: export Props interface (TS4023 under composite builds;
  re-apply if regenerated)

lint:check, format:check, build: green. No Vuetify usage left in the
migrated files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…upling

- Multi-step flows (save/replace/delete process model) now run in one
  transaction: class-level @transactional on the repository impls and
  the orchestrating usecase methods; includes rollback test that
  fails if the annotation is removed. Fixed two latent bugs surfaced
  by the shared persistence context (managed-entity mutation during
  connection copy; flush/clear before bulk delete)
- ProcessModelRepository port no longer leaks the JPA entity
  ProcessModelTable: returns ProcessModelReference record; usecases
  package now imports zero repository.tables types
- JAX-RS ExceptionMappers (403/404/400/500) replace per-endpoint
  try/catch; bodies keep the shape the frontend reads
  (exceptionType); generic 500 logs without leaking internals
- JPA entity hygiene: id-based equals + constant hashCode for
  ProcessModelTable (lives in HashSets, Lombok hashed mutable/lazy
  state) and missing hashCode on ProjectVersionTable
- SearchQueryHelper dedups the levenshtein-vs-exact db-kind
  conditional across four DAOs

Tests: 358 green (+13, -1 obsolete).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vuetify ships an unlayered element reset (div { padding: 0 } etc.).
Unlayered CSS beats every @layer rule, so all padding/margin
utilities in shadcn components computed to 0. Import the utilities
WITHOUT layer(...) so they win by class specificity during the
coexistence phase. Verified live: dialog p-6 now applies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend (adversarially verified by the branch review):
- DOCTYPE pre-check falsely rejected BPMN with '<!DOCTYPE' as inert
  text (CDATA scripts, comments). Removed; ModelParseExceptionMapper
  now turns parser rejections into a clean 400 without leaking
  internals. Regression fixture proves CDATA doctype text uploads fine
- JWT audience was never actually enforced: smallrye reads
  mp.jwt.verify.audiences (plural), all profiles used the singular
  key. Renamed in all four profiles; negative tests (missing/wrong
  aud -> 401) lock the enforcement in. Tests: 362 green

Infra/docs:
- make setup bootstraps frontend/.env (fresh clones ran the frontend
  in desktop mode against a web backend)
- Released desktop jars are now a real artifact: db-kind is fixed at
  build time, so release.yml builds pro-a-*-desktop.jar with the
  desktop profile; README/properties comments corrected
- pre-push fallback emits one path per line (it silently skipped all
  frontend checks before)
- ARCHITECTURE.md frontend tree updated to the post-branch reality

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…id, polish

- All Tailwind utilities now use the tw: prefix (components.json
  prefix + regenerated ui components + rewritten feature files).
  Vuetify ships unlayered !important utilities with identical names
  (.border, .rounded-lg, runtime .bg-primary) that overrode shadcn
  styling - badges rendered Vuetify light-blue, buttons lost hover,
  borders broke. Verified in the built CSS and in the running app
- Portaled layers (dialog/select/dropdown/tooltip/sheet) raised to
  tw:z-[2400] so they stack above Vuetify's app bar; exceptions
  documented in docs/UI-MIGRATION.md
- C8 import sent the project id where the backend expects a project
  VERSION id - imports in web mode hit the new authorization check
  and failed. Now resolves the active version id
- 401 auto-logout also closes any open auth dialog
- Live field validation restored (reward-early-punish-late watchers)
- Dialogs get sr-only DialogDescriptions (Reka a11y warnings gone)
- setMode.mjs no longer rewrites the production application.properties
- SignIn height accounts for Vuetify's app bar (no phantom scrollbar)
- prettier scope aligned across hook/CI/scripts (+ .prettierignore)
- docs/IMPROVEMENTS.md committed (was referenced but untracked)

lint:check, format:check, build green; flows re-verified in the
running app via Playwright.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ADR-0001: replace the homegrown identity stack with Keycloak/OIDC
(quarkus-oidc bearer verification, oidc-client-ts PKCE flow, local
user provisioning via CurrentUserService); authorization stays in
the application. Evidence: the June 2026 review found exploitable
defects exactly in the self-built layer.

ADR-0002: framework-free diagram core as yarn workspace packages,
consolidate rendering on diagram-js (port the JointJS map, elkjs
layout), TS relation detection kept honest against the Java
implementation via a shared conformance fixture suite; VS Code
extension consumes the core via a workspace adapter. Sequenced
after ADR-0001.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keycloak 26.6 on port 8181 via docker-compose; realm 'proa' with
public PKCE client proa-frontend, realm roles User/Admin (User is
default on registration), registration + password reset enabled,
short access tokens (5m) with SSO session, brute-force protection,
and two dev users (admin@proa.local/admin, user@proa.local/user).
Verified: realm imports and OIDC discovery responds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- oidc-client-ts 3.5.0: Authorization Code + PKCE against the proa
  realm; session in sessionStorage via the library (userToken/userRole
  no longer persisted by the store - single source of truth)
- Router guard (web mode) lazily initializes auth and redirects
  unauthenticated users to Keycloak; new /signin-callback view;
  desktop mode never loads the OIDC machinery (separate chunk)
- 401 interceptor now clears auth state and re-enters the OIDC flow
- Removed: SignIn view, CreateAccount, ChangePassword, api/auth
  (login/register) - registration, credentials and password reset
  live in Keycloak now; profile dialogs show email read-only with an
  SSO hint and link to the Keycloak account console
- Roles derived from realm_access.roles in the access token
- i18n: new SSO keys (en/de), ~25 dead auth keys removed

lint:check, format:check, build: green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th (ADR-0001)

- quarkus-oidc (service type) verifies bearer tokens; realm roles map
  via realm_access/roles; issuer enforced (audience deliberately not:
  Keycloak public-client tokens carry aud=account - documented)
- New request-scoped CurrentUserService: provisions the local
  UserTable row on first authenticated request (email/name claims),
  syncs the role from the token; replaces every jwt userId-claim parse
- Deleted: TokenService, AuthenticationResource (login/register),
  lockout + AuthenticationRepository, AdminInitializer, bucket4j rate
  limiting, elytron/bcrypt, jjwt, generate-keys.sh and runtime key
  handling; UserTable lost password/failedLoginAttempts; self-service
  email changes return 400 (identity provider owns email)
- Test profiles: desktop bulk runs with OIDC tenant disabled; web-mode
  profile verifies against the checked-in test public key (no
  container): 401/403/200 + provisioning and role-sync tests
- Makefile/README/deploy.yml updated: no key generation step; web-mode
  dev needs 'make auth-up'; prod requires QUARKUS_OIDC_AUTH_SERVER_URL
  and VITE_OIDC_* repo vars

Tests: 336 green (net deletion of the homegrown-auth suite).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Parallel first requests of a freshly logged-in user each created a
  local row (no unique constraint on email) -> NonUniqueResultException
  on every subsequent lookup. UserTable.email is now unique and
  CurrentUserService recovers from a lost insert race by re-reading
  the winner's row; no outer transaction so the constraint violation
  cannot poison caller work
- dev/desktop H2 profiles pin default-schema=PUBLIC (they inherited
  the prod ${STAGE:local} default and spammed schema-not-found DDL
  warnings)

Verified live: Keycloak login -> exactly one provisioned user with
role/names from token claims; logout ends the SSO session. Backend
suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identity is Keycloak's domain; the app's domain is project membership.
Removes ManageUsers/admin endpoints/local profile editing and the
app-level Admin role; adds pending invitations resolved at first
login - invitees no longer need to have logged in before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- deploy.yml fails fast when web mode is deployed without the
  VITE_OIDC_* repo variables (previously baked empty values)
- backend/.gitignore re-ignores legacy generated JWT keys so old
  checkouts cannot accidentally track them
- make db-reset scoped to the db service (no longer destroys the
  Keycloak realm); Makefile help texts tell the truth about Docker
- ARCHITECTURE.md/UI-MIGRATION.md updated to the post-ADR-0001 state
  (authservice/startup gone, api/auth modules, sign-in via Keycloak)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- UserTable identity key is now the immutable oidcSubject (unique);
  email demoted to synced profile data (recycled addresses must not
  inherit the old owner's account - review finding). Legacy rows are
  claimed exactly once via guarded UPDATE on the owner's next
  verified login; recycled emails get a fresh row
- New ProjectInvitationTable + invitation lifecycle: inviting an
  unknown email stores a pending invitation (idempotent); known email
  becomes a membership immediately; CurrentUserService redeems
  pending invitations on login - only with email_verified=true (an
  unverified self-registration must not collect invitations, nor
  claim a legacy row)
- New endpoints: GET/DELETE /api/project/{id}/invitation[/{invId}]
  (owner-only); contributor POST returns MEMBER_ADDED vs
  INVITATION_PENDING; project deletion cleans up its invitations
- Removed per ADR-0003: admin user endpoints (list/patch/delete),
  self-PATCH profile (Keycloak account console owns profile data)
- Race-safety: unique (user,project) on memberships, PESSIMISTIC_WRITE
  on redemption, guarded claim UPDATE; 31 new tests cover the races,
  recycling and the REST lifecycle

Tests: 372 green (+36).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ADR-0003)

- Deleted ManageUsers view/route/AppBar button, EditUserDialog,
  EditProfileDialog and the requiresAdmin gating; ProfileDialog is
  read-only with the Keycloak account-console link
- ProjectDetailDialog: 'Invite' distinguishes member-added vs
  invitation-sent, shows pending invitations with revoke (shadcn,
  owner errors via snackbar); api/projects gained inviteMember/
  getInvitations/revokeInvitation, api/users slimmed to getCurrentUser
- OIDC fixes from the adversarial review: redirectOnce lock released
  on settle (browser-Back no longer bricks sign-in), 401 redirect
  loop breaker (sessionStorage marker, max 2 attempts/30s), silent
  renew error retried once before the session lapses, original
  AxiosError preserved when Keycloak is unreachable, transient
  selectedDialog no longer persisted

lint:check, format:check, build: green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- application.properties: prod datasource no longer hardcodes the
  Azure Postgres host - fully env-driven via QUARKUS_DATASOURCE_URL/
  USERNAME/PASSWORD (also removes the review's 'dev and prod share one
  Azure DB/login' coupling). default-schema=${STAGE:local} kept as a
  generic optional schema selector
- deploy.yml: replaces Azure ACR login + azure/webapps-deploy with a
  vendor-neutral container publish to GitHub Container Registry
  (ghcr.io/<repo>:dev|prod + :sha). The deploy target is now
  decoupled - the image is published, where it runs is a separate ops
  decision that supplies QUARKUS_DATASOURCE_* and
  QUARKUS_OIDC_AUTH_SERVER_URL. ADR-0001 frontend .env guard preserved
- README: vendor-neutral managed-Postgres fuzzystrmatch note; also
  drops the Gemini line from the settings docs (the feature itself is
  removed in the companion commit; it returns later via MCP)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The process-description AI generator called Google's API directly from
the browser with a key fetched from /api/settings - the long-standing
client-side-secret finding. Removed entirely; an AI description feature
will be re-added later through an MCP server instead.

Frontend: drop @google/generative-ai; remove the generate-description
affordance and its dead error/loading state in ProcessList; remove the
Gemini API-key section from SettingsDrawer; drop geminiApiKey from the
Settings type, the ProcessMap settings backfill and the i18n keys.
Backend: drop geminiApiKey from the Settings entity/table/mapping and
its test assertions; Camunda Modeler/Operate settings are untouched.

Note: prod hibernate generation=update does not drop columns, so the
existing gemini_api_key column lingers harmlessly until a real
migration; no data action needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dominikhorn93 and others added 7 commits June 17, 2026 22:06
- ProjectOverview: tiles -> Card, version v-select -> Select, the three
  dialogs -> Dialog (delete-confirm keeps the XSS-safe <i18n-t> slots)
- ProjectDetailDialog: outer dialog/card -> shadcn; the project-invitation
  UI and its api calls preserved unchanged
- ProcessList: toolbar/list/FABs/dialogs -> shadcn; v-file-input -> styled
  native file input; v-progress-* -> Progress/Loader2; BPMN upload/parse
  logic and api/processModels calls untouched
- ProcessTreeNode: recursive list-group -> div rows with local expand state
- PageNotFound: centered shadcn layout
- New ui/progress component (CLI, pins restored)

Vuetify shell + global snackbar intentionally untouched (migrated last).
Vuetify grep over the 5 files: 0 hits. lint/format/build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CamundaCloudImport: toolbar/list/checkboxes/dialogs/FABs -> shadcn;
  email filter v-menu -> Popover; api/camundaCloud calls intact
  (importProcessModels still passes the project VERSION id)
- ProcessModel + ProcessDetailDialog: Vuetify chrome -> shadcn; the
  bpmn-js viewers themselves untouched
- ProcessMap chrome (Toolbar/Sidebar/Legend/LegendItem/NavigationButtons)
  -> shadcn; sidebar is a non-modal fixed panel (must not dim the canvas)
- ProcessMap.vue: only the Vuetify chrome migrated (loading, mouse-following
  tooltip, wrapper). The ENTIRE JointJS canvas (paper, graph, element
  classes, zoom/pan/layout, persistence) left untouched for the
  diagram-js extraction (ADR-0002)
- New ui/popover (tw:z-[2400], pins restored)

Vuetify shell + snackbar still intentionally present (migrated next).
Vuetify grep over the 9 files: 0 hits. lint/format/build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Default/AppBar/View/SettingsDrawer shell migrated to shadcn/Tailwind:
  v-app/v-main -> flex layout; v-app-bar -> sticky header; nav drawer
  and SettingsDrawer -> Sheet; language menu -> DropdownMenu
- Global snackbar -> vue-sonner: store.showSnackbar keeps its
  (message, type) signature, body now calls toast.*; dead snackbar
  state and SnackbarConfigs removed
- Vuetify fully removed: vuetify, vite-plugin-vuetify, @mdi/font,
  webfontloader, roboto-fontface, @types/webfontloader deps gone;
  plugins/vuetify.ts + webfontloader.ts, styles/settings.scss +
  global.css deleted; vuetify() out of vite.config; tsconfig types
  pruned. vue-sonner pinned 2.0.9
- Header fixed at 64px and <main> at calc(100vh-4rem) to preserve the
  definite-height ancestor the JointJS/bpmn-js canvases depend on
- shadcn.css left as-is: Tailwind preflight enabling is a deliberate
  separate follow-up

Typography now uses Tailwind's default sans (Roboto was Vuetify's).
No v-* / Vuetify left except stale comment lines in shadcn.css.
lint/format/build green; canvases verified next in the running app.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without preflight there was no base element reset: native <button>
chrome showed through ghost buttons and there was no base font-family
(everything rendered in the browser's serif default). Preflight is
imported in layer(base); utilities stay unlayered so they keep winning
by specificity. Verified in the running app: sans-serif typography and
clean header icon buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three issues surfaced when rendering the map with real data (first time
this session); none were the @joint version skew first suspected:

1. Crash in @joint/layout-directed-graph: its toGraphLib builds the
   graphlib graph via setEdge(source.id, target.id), silently creating
   a phantom node for any endpoint that is not a real element, then
   crashes in importElement (getCell -> undefined). A single link
   ending at a bare point also aborts its cell loop (it uses break, not
   continue). Guard before layout: drop links whose endpoints are
   missing/not real elements - they cannot be routed on the map anyway.
2. Canvas height collapsed to 0 (regression from the shadcn shell
   migration): .full-screen-below-toolbar used height: calc(100% - 64px),
   but percentage heights no longer resolve because the routed <main> is
   a flex-1 item inside a min-h-screen column (indefinite for percentage
   children). Switched to a viewport-based calc(100vh - 128px).
3. View.vue: tidy the arbitrary value to calc(100vh_-_4rem).

Verified in the running app (Keycloak + backend + Vite): uploaded a
BPMN, opened the map, the process node renders with all ports. Known
minor nit: auto-fit on first load of a single isolated node doesn't
recenter (manual fit works) - left for the diagram-js renderer
extraction (ADR-0002) that replaces this layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings:
- HIGH: vue-sonner ships its stylesheet separately and does no runtime
  style injection, so the toaster (store.showSnackbar -> toast) rendered
  with no positioning/background/animation. Import vue-sonner/style.css
  in main.ts. Verified: 98 data-sonner rules now in the bundled CSS.
  (My E2E missed this - toasts flashed but I didn't inspect their style.)
- sass was only used by the deleted Vuetify settings.scss; removed from
  devDependencies and pruned from the lockfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ling

Review found the docs stale against this branch's changes:
- UI-MIGRATION.md: migration is COMPLETE - all areas shadcn, snackbar
  vue-sonner, Vuetify removed, preflight enabled; removal checklist
  ticked; only the JointJS map canvas remains (ADR-0002). Optional
  follow-ups listed (canonicalize shadcn.css, revert z-[2400], map
  auto-fit nit)
- ARCHITECTURE.md: plugins no longer Vuetify; UI-migration deviation
  marked complete; Gemini note removed; deploy bullet now GHCR/decoupled
- IMPROVEMENTS.md: mark the Gemini-key-in-browser and Azure-shared-DB
  action items resolved on this branch; coexistence note updated to
  'Vuetify fully removed'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant