diff --git a/.claude/skills/zammad-compat/SKILL.md b/.claude/skills/zammad-compat/SKILL.md new file mode 100644 index 0000000..3c267c4 --- /dev/null +++ b/.claude/skills/zammad-compat/SKILL.md @@ -0,0 +1,197 @@ +--- +name: zammad-compat +description: Check upstream Zammad for breaking changes before upgrading the addon +disable-model-invocation: true +argument-hint: "[target-version]" +allowed-tools: Bash(git clone *), Bash(git -C /tmp/zammad-upstream *) +--- + +# Zammad Upstream Compatibility Check + +Check the upstream zammad/zammad repository for changes that could break or require updates to our Zammad addon (`packages/zammad-addon-link`). + +## Arguments + +- `$ARGUMENTS` - Optional: target Zammad version/tag/branch to compare against (e.g. `6.6.0`, `stable`). If not provided, ask the user what version to compare against. The current version is in `docker/zammad/Dockerfile` as the `ZAMMAD_VERSION` ARG. + +## Setup + +1. Read the current Zammad version from `docker/zammad/Dockerfile` (the `ARG ZAMMAD_VERSION=` line). +2. Clone or update the upstream Zammad repository: + - If `/tmp/zammad-upstream` does not exist, clone it: `git clone --bare https://github.com/zammad/zammad.git /tmp/zammad-upstream` + - If it exists, update it: `git -C /tmp/zammad-upstream fetch --all --tags` +3. Determine the version range. The current version is the `ZAMMAD_VERSION` from step 1. The target version is the argument or user-provided version. Both versions should be used as git refs (tags are typically in the format `X.Y.Z`). + +## Checks to Perform + +Run ALL of these checks and compile results into a single report. + +### 1. Replaced Stock Files + +These are stock Zammad files that our addon REPLACES with modified copies. Changes upstream mean we need to port those changes into our modified versions. + +For each file below, diff the upstream version between the current and target version. Report any changes found. + +**Vue/TypeScript (Desktop UI):** +- `app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/ArticleReply.vue` +- `app/frontend/apps/desktop/pages/personal-setting/views/PersonalSettingNotifications.vue` +- `app/frontend/apps/desktop/components/Form/fields/FieldNotifications/FieldNotificationsInput.vue` +- `app/frontend/apps/desktop/components/Form/fields/FieldNotifications/types.ts` + +**CoffeeScript (Legacy UI):** +- `app/assets/javascripts/app/controllers/_profile/notification.coffee` +- `app/assets/javascripts/app/controllers/_ui_element/notification_matrix.coffee` +- `app/assets/javascripts/app/lib/mixins/ticket_notification_matrix.coffee` +- `app/assets/javascripts/app/views/generic/notification_matrix.jst.eco` +- `app/assets/javascripts/app/views/profile/notification.jst.eco` + +Command pattern for each file: +```bash +git -C /tmp/zammad-upstream diff -- +``` + +If a file does not exist at either version, note that (it may have been added, removed, or renamed). + +### 2. Monkey-Patched Files + +These are files our addon patches at runtime via Ruby `prepend`, `include`, or `after_initialize` hooks. Changes to these files could break our patches. + +**Search Backend (OpenSearch compatibility patch):** +- `lib/search_index_backend.rb` - We prepend `SearchIndexBackendOpenSearchPatch` to override `_mapping_item_type_es`. Check if this method signature or the `'flattened'` string usage has changed. + +**Core Models (callback injection targets):** +- `app/models/ticket/article.rb` - We inject `after_create` callbacks via `include` for Signal and WhatsApp message delivery. Check for changes to the callback chain, model structure, or the `Sender`/`Type` lookup patterns. +- `app/models/link.rb` - We inject an `after_create` callback for Signal group setup on ticket split. Check for structural changes. + +**Transaction System:** +- `app/models/transaction/` directory - We register `Transaction::SignalNotification` as backend `0105_signal_notification`. Check if the transaction backend system has been refactored. + +**Icons:** +- `public/assets/images/icons.svg` - Our initializers append SVG icons at boot time. Check if the SVG structure or the icon injection mechanism has changed. + +Command pattern: +```bash +git -C /tmp/zammad-upstream diff -- +``` + +For the search backend specifically, also check if `_mapping_item_type_es` still exists and still returns `'flattened'`: +```bash +git -C /tmp/zammad-upstream show :lib/search_index_backend.rb | grep -n -A5 '_mapping_item_type_es\|flattened' +``` + +### 3. API Surface Dependencies + +These are Zammad APIs/interfaces/mixins our addon relies on. Changes could cause runtime failures. + +**Channel Driver Interface:** +- `app/models/channel/driver/` - Check if the driver base class or interface expectations have changed (methods: `fetchable?`, `disconnect`, `deliver`, `streamable?`). + +**Controller Concerns:** +- `app/controllers/concerns/creates_ticket_articles.rb` - Used by our webhook controllers. Check for interface changes. + +**Ticket Article Types & Senders:** +- `app/models/ticket/article/type.rb` and `app/models/ticket/article/sender.rb` - We look up types by name (`'signal message'`, `'whatsapp message'`). Check for changes in how types are registered or looked up. + +**Authentication/Authorization:** +- `app/policies/` directory structure - We create policies matching `controllers/` names. Check if the policy naming convention or base class has changed. + +**Package System:** +- `lib/package.rb` or the package install/uninstall API - We use `Package.install(file:)` and `Package.uninstall(name:, version:)` in setup.rb. + +**Scheduler/Job System:** +- `app/jobs/` base class patterns - Our jobs inherit from ApplicationJob. Check for changes. + +Command pattern: +```bash +git -C /tmp/zammad-upstream diff --stat -- +git -C /tmp/zammad-upstream diff -- +``` + +### 4. Path Collision Detection + +Check if the target Zammad version has added any NEW files at paths that collide with our addon files. Our addon installs files at these paths: + +**Controllers:** `app/controllers/channels_cdr_signal_controller.rb`, `channels_cdr_voice_controller.rb`, `channels_cdr_whatsapp_controller.rb`, `cdr_signal_channels_controller.rb`, `cdr_ticket_article_types_controller.rb`, `formstack_controller.rb`, `opensearch_controller.rb` + +**Models:** `app/models/channel/driver/cdr_signal.rb`, `cdr_whatsapp.rb`, `app/models/ticket/article/enqueue_communicate_cdr_signal_job.rb`, `enqueue_communicate_cdr_whatsapp_job.rb`, `app/models/link/setup_split_signal_group.rb`, `app/models/transaction/signal_notification.rb` + +**Jobs:** `app/jobs/communicate_cdr_signal_job.rb`, `communicate_cdr_whatsapp_job.rb`, `signal_notification_job.rb`, `create_ticket_from_form_job.rb` + +**Libraries:** `lib/cdr_signal.rb`, `cdr_signal_api.rb`, `cdr_signal_poller.rb`, `cdr_whatsapp.rb`, `cdr_whatsapp_api.rb`, `signal_notification_sender.rb` + +**Routes:** `config/routes/cdr_signal_channels.rb`, `channel_cdr_signal.rb`, `channel_cdr_voice.rb`, `channel_cdr_whatsapp.rb`, `cdr_ticket_article_types.rb`, `formstack.rb`, `opensearch.rb` + +**Frontend Plugins:** `app/frontend/shared/entities/ticket-article/action/plugins/cdr_signal.ts`, `cdr_whatsapp.ts`, `app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/article-type/plugins/signalMessage.ts`, `whatsappMessage.ts` + +Check if any of these paths exist in the target version: +```bash +for path in ; do + git -C /tmp/zammad-upstream show :$path 2>/dev/null && echo "COLLISION: $path exists upstream" +done +``` + +### 5. Dockerfile Patch Targets + +Check files that are patched at Docker build time via `sed`: + +- `lib/search_index_backend.rb` - `sed` replaces `'flattened'` with `'flat_object'`. Verify the string still exists in the target version. +- `contrib/nginx/zammad.conf` - Structure modified for embedded mode. Check for format changes. +- `docker-entrypoint.sh` - We inject addon install commands after the `# es config` comment. Verify this comment/anchor still exists. + +Check the upstream Docker entrypoint: +```bash +git -C /tmp/zammad-upstream show :contrib/docker/docker-entrypoint.sh 2>/dev/null | grep -n 'es config' || echo "Anchor comment not found - check entrypoint structure" +``` + +Also check the Zammad Docker Compose repo if relevant (the base image may come from `zammad/zammad-docker-compose`). + +### 6. Database Schema Conflicts + +Check if the target Zammad version adds any columns or tables that could conflict with our migrations: +- Column names: `whatsapp_uid`, `signal_uid`, `signal_username` on the users table +- Setting names containing: `signal_notification`, `cdr_link`, `formstack`, `opensearch_dashboard` + +```bash +git -C /tmp/zammad-upstream diff -- db/migrate/ | grep -i 'signal\|whatsapp\|formstack\|opensearch' +``` + +### 7. Frontend Build System + +Check if the Vite/asset pipeline configuration has changed significantly, since our addon relies on being compiled into the Zammad frontend: + +```bash +git -C /tmp/zammad-upstream diff --stat -- vite.config.ts app/frontend/vite.config.ts config/initializers/assets.rb Gemfile +``` + +Also check if CoffeeScript/Sprockets support has been removed (would break our legacy UI files): +```bash +git -C /tmp/zammad-upstream show :Gemfile 2>/dev/null | grep -i 'coffee\|sprockets' +``` + +## Report Format + +Compile all findings into a structured report: + +``` +## Zammad Compatibility Report: -> + +### CRITICAL (Action Required Before Upgrade) +- [List files that changed upstream AND are replaced by our addon - these need manual merging] +- [List any broken monkey-patch targets] +- [List any path collisions] + +### WARNING (Review Needed) +- [List API surface changes that could affect our code] +- [List Dockerfile patch targets that changed] +- [List build system changes] + +### INFO (No Action Needed) +- [List files checked with no changes] +- [List confirmed-safe paths] + +### Recommended Actions +- For each CRITICAL item, describe what needs to be done +- Note any files that should be re-copied from upstream and re-patched +``` + +For each changed file in CRITICAL, show the upstream diff so the user can see what changed and decide how to integrate it. diff --git a/.gitignore b/.gitignore index 2bea406..44a541f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,11 @@ ENVIRONMENT_VARIABLES_MIGRATION.md local-scripts/* docs/ packages/zammad-addon-link/test/ + +# Allow Claude Code project config (overrides global gitignore) +!CLAUDE.md +!.claude/ +.claude/** +!.claude/skills/ +!.claude/skills/** +.claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a29fba6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,114 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is a monorepo for CDR Link - a Zammad addon and supporting services built by the Center for Digital Resilience. It adds Signal, WhatsApp, and voice channel support to Zammad via a custom `.zpm` addon package, along with a standalone WhatsApp bridge service. It uses pnpm workspaces and Turborepo for orchestration. + +**Tech Stack:** +- Zammad 6.5.x as the core helpdesk platform +- Ruby (Rails initializers, controllers, models, jobs) for the Zammad addon +- TypeScript/Node.js for build tooling and the WhatsApp bridge +- CoffeeScript for Zammad legacy UI extensions +- Vue 3 for Zammad desktop UI extensions +- Docker for containerization +- PostgreSQL, Redis, Memcached as backing services + +## Project Structure + +``` +apps/ + bridge-whatsapp/ # Standalone WhatsApp bridge (Hapi.js + Baileys) +packages/ + zammad-addon-link/ # Zammad addon source (Ruby, CoffeeScript, Vue, TS) + src/ # Addon source files (installed into /opt/zammad/) + scripts/build.ts # Builds .zpm package from src/ + scripts/migrate.ts # Generates new migration stubs +docker/ + zammad/ # Custom Zammad Docker image + Dockerfile # Extends zammad/zammad-docker-compose base image + install.rb # Extracts addon files from .zpm at build time + setup.rb # Registers addon packages at container startup + addons/ # Built .zpm files (gitignored, generated by turbo build) + compose/ # Docker Compose service definitions +``` + +## Common Development Commands + +```bash +pnpm install # Install all dependencies +turbo build # Build all packages (generates .zpm files) +npm run docker:zammad:build # Build custom Zammad Docker image +npm run docker:all:up # Start all Docker services +npm run docker:all:down # Stop all Docker services +npm run docker:zammad:restart # Restart railsserver + scheduler (after Ruby changes) +npm run update-version # Update version across all packages +npm run clean # Remove all build artifacts and dependencies +``` + +## Zammad Addon Architecture + +### Addon Build & Deploy Pipeline + +1. `turbo build` runs `tsx scripts/build.ts` in `packages/zammad-addon-link/` +2. Build script base64-encodes all files under `src/`, produces `docker/zammad/addons/zammad-addon-link-v{version}.zpm` +3. `docker/zammad/Dockerfile` builds a custom image: + - Copies `.zpm` files and runs `install.rb` to extract addon files into the Zammad directory tree + - Rebuilds Vite frontend (`bundle exec vite build`) to include addon Vue components + - Precompiles assets (`rake assets:precompile`) to include addon CoffeeScript + - Applies `sed` patches (OpenSearch compatibility, entrypoint injection) +4. At container startup, `setup.rb` registers the addon via `Package.install()` and runs migrations + +### How the Addon Extends Zammad + +**New files (no upstream conflict risk):** Controllers, channel drivers, jobs, routes, policies, library classes, views, CSS, SVG icons, frontend plugins. These add Signal/WhatsApp/voice channel support. + +**Replaced stock files (HIGH conflict risk - must be manually merged on Zammad upgrades):** +- `app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/ArticleReply.vue` - Adds channel whitelist filtering via `cdr_link_allowed_channels` setting +- `app/frontend/apps/desktop/pages/personal-setting/views/PersonalSettingNotifications.vue` - Adds Signal notification recipient field +- `app/frontend/apps/desktop/components/Form/fields/FieldNotifications/FieldNotificationsInput.vue` - Adds Signal column to notification matrix +- `app/frontend/apps/desktop/components/Form/fields/FieldNotifications/types.ts` - Extended notification types +- `app/assets/javascripts/app/controllers/_profile/notification.coffee` - Signal notification prefs (legacy UI) +- `app/assets/javascripts/app/controllers/_ui_element/notification_matrix.coffee` - Signal column (legacy UI) +- `app/assets/javascripts/app/lib/mixins/ticket_notification_matrix.coffee` - Notification matrix mixin +- `app/assets/javascripts/app/views/generic/notification_matrix.jst.eco` - Notification matrix template +- `app/assets/javascripts/app/views/profile/notification.jst.eco` - Notification profile template + +**Runtime monkey-patches (HIGH conflict risk):** +- `config/initializers/opensearch_compatibility.rb` - Prepends to `SearchIndexBackend._mapping_item_type_es()` to replace `'flattened'` with `'flat_object'` for OpenSearch +- `config/initializers/cdr_signal.rb` - Injects `after_create` callbacks into `Ticket::Article` and `Link` models +- `config/initializers/cdr_whatsapp.rb` - Injects `after_create` callback into `Ticket::Article` + +**Dockerfile-level patches:** +- `lib/search_index_backend.rb` - `sed` replaces `'flattened'` with `'flat_object'` +- `/docker-entrypoint.sh` - `sed` injects addon install commands after `# es config` anchor +- `contrib/nginx/zammad.conf` - Adds `/link` proxy location in embedded mode + +### Key Zammad API Dependencies + +The addon depends on these Zammad interfaces remaining stable: +- `Channel::Driver` interface (`fetchable?`, `disconnect`, `deliver`, `streamable?`) +- `Ticket::Article` model callbacks and `Sender`/`Type` lookup by name +- `Link` model and `Link::Type`/`Link::Object` +- `SearchIndexBackend._mapping_item_type_es` method +- `Transaction` backend registration system +- `Package.install(file:)` / `Package.uninstall(name:, version:)` API +- `CreatesTicketArticles` controller concern +- Policy naming convention (`controllers/_controller_policy.rb`) + +## Zammad Development Notes + +- After changing any Ruby files, restart railsserver and scheduler: `npm run docker:zammad:restart` +- The addon must be rebuilt (`turbo build`) and the Docker image rebuilt (`npm run docker:zammad:build`) for changes to take effect in Docker +- Use `/zammad-compat ` to check upstream Zammad for breaking changes before upgrading +- The current Zammad base version is set in `docker/zammad/Dockerfile` as `ARG ZAMMAD_VERSION` + +## Docker Services + +Defined in `docker/compose/`: +- **zammad.yml**: zammad-init, zammad-railsserver, zammad-nginx, zammad-scheduler, zammad-websocket, zammad-memcached, zammad-redis +- **bridge-whatsapp.yml**: bridge-whatsapp +- **postgresql.yml**: postgresql +- **signal-cli-rest-api.yml**: signal-cli-rest-api +- **opensearch.yml**: opensearch + dashboards