diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..790624b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,54 @@ +--- +name: Bug +about: Plan and track a bug fix slice +title: "Bug: " +labels: ["type:bug"] +assignees: [] +--- + +## Goal + + +## Scope + + +## Branch +`bug/` + +## Bug Details +### Current Behavior + + +### Expected Behavior + + +### Steps to Reproduce +1. ... +2. ... +3. ... + +## Acceptance Criteria +- [ ] Root cause is identified and fixed. +- [ ] Behavior matches expected result in affected flow. +- [ ] No regression is introduced in related flow. + +## Test Checklist +### Unit +- [ ] Add or update unit tests for the bug scenario. + +### Integration +- [ ] Add or update integration tests if cross-component behavior changed. + +### E2E +- [ ] Add or update E2E coverage when user flow is affected. + +### Manual +- [ ] Reproduce bug on current main/develop baseline. +- [ ] Verify fix with same repro steps after patch. + +## Notes + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f7b6465 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions and support + url: https://github.com/mjkatgithub/Decentra/discussions + about: Please ask questions in Discussions. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/epic.md b/.github/ISSUE_TEMPLATE/epic.md new file mode 100644 index 0000000..3b18f77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic.md @@ -0,0 +1,24 @@ +--- +name: Epic +about: Group related feature issues under one milestone goal +title: "Epic: " +labels: ["type:epic"] +assignees: [] +--- + +## Goal + + +## Scope +- [ ] Feature issue 1 +- [ ] Feature issue 2 +- [ ] Feature issue 3 + +## Definition of Done +- [ ] All feature issues are created and linked as sub-issues +- [ ] All feature issues are assigned to the same milestone +- [ ] All feature issues are added to the project board +- [ ] Every feature issue has acceptance criteria and test checklist + +## Notes + diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..b2a7c57 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,36 @@ +--- +name: Feature +about: Plan and implement a feature slice +title: "Feature: " +labels: ["type:feature"] +assignees: [] +--- + +## Goal + + +## Scope + + +## Branch +`feature/` (Feature) + +## Acceptance Criteria +- [ ] ... +- [ ] ... +- [ ] ... + +## Test Checklist +### Unit +- [ ] ... + +### Integration +- [ ] ... + +### E2E +- [ ] ... + +## Notes + diff --git a/.github/PULL_REQUEST_TEMPLATE/default.md b/.github/PULL_REQUEST_TEMPLATE/default.md new file mode 100644 index 0000000..7fb7590 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/default.md @@ -0,0 +1,35 @@ +## Summary + + +## Related Issue + + +## Branch Flow +- Source branch: `feature/` or `bug/` +- Target branch: `develop` + +## Scope of Changes +- [ ] Code changes are limited to the issue scope +- [ ] No unrelated refactors are included + +## Validation +### Local checks +- [ ] Unit tests pass +- [ ] Integration tests pass (if relevant) +- [ ] E2E smoke passes (if relevant) + +### Manual test +- [ ] Happy path verified +- [ ] Edge cases verified + +## Risk and Rollback +- Risk level: [ ] Low [ ] Medium [ ] High +- Rollback plan: + - Revert this PR + - Disable affected feature path if needed + +## Checklist +- [ ] Issue is linked and updated +- [ ] Acceptance criteria are met +- [ ] Test checklist in the issue is completed +- [ ] No secrets or credentials are included diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md new file mode 100644 index 0000000..499b03b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -0,0 +1,39 @@ +## Release Summary + + +## Branch Flow +- Source branch: `develop` +- Target branch: `master` + +## Included Work +- [ ] Milestone completed (example: Phase 3) +- [ ] Linked feature/bug PRs are merged into `develop` +- [ ] Release notes draft is prepared + +## Compatibility and Risk +- Breaking changes: [ ] No [ ] Yes (describe below) +- Migration required: [ ] No [ ] Yes (describe below) +- Risk level: [ ] Low [ ] Medium [ ] High + +### Breaking changes / migration notes + + +## Validation +### Pre-release checks +- [ ] CI pipelines passed on `develop` +- [ ] Smoke tests passed +- [ ] Critical user flows validated manually + +### Post-merge plan +- [ ] Verify production/staging health +- [ ] Monitor logs and error tracking +- [ ] Prepare rollback if required + +## Rollback Plan +- Revert release merge commit on `master` +- Hotfix from `master` if partial rollback is needed + +## Checklist +- [ ] CHANGELOG updated +- [ ] Version/tag strategy confirmed +- [ ] Stakeholder communication prepared diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..3595406 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,88 @@ +name: Changelog Check + +on: + pull_request: + branches: + - develop + - master + +jobs: + changelog: + name: Changelog policy + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Enforce changelog policy by target branch + run: | + set -euo pipefail + + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + BASE_REF="${{ github.base_ref }}" + HEAD_REF="${{ github.head_ref }}" + + if ! git diff --name-only "$BASE_SHA" "$HEAD_SHA" | \ + grep -q '^CHANGELOG.md$'; then + echo "ERROR: CHANGELOG.md was not updated." + exit 1 + fi + + extract_unreleased() { + awk ' + /^## \[Unreleased\]/ { in_section=1; next } + /^## \[/ { if (in_section) exit } + in_section { print } + ' + } + + count_entries() { + grep -E -c '^[[:space:]]*-[[:space:]]+' || true + } + + BASE_UNRELEASED="$(git show "$BASE_SHA:CHANGELOG.md" | \ + extract_unreleased)" + HEAD_UNRELEASED="$(git show "$HEAD_SHA:CHANGELOG.md" | \ + extract_unreleased)" + + BASE_COUNT="$(printf '%s\n' "$BASE_UNRELEASED" | count_entries)" + HEAD_COUNT="$(printf '%s\n' "$HEAD_UNRELEASED" | count_entries)" + + if [ "$BASE_REF" = "develop" ]; then + # Feature/Bug/etc -> develop: + # PR must add at least one Unreleased entry. + if [ "$HEAD_COUNT" -le "$BASE_COUNT" ]; then + echo "ERROR: PR into develop must add an Unreleased entry." + echo "Base Unreleased entries: $BASE_COUNT" + echo "Head Unreleased entries: $HEAD_COUNT" + exit 1 + fi + + echo "OK: Unreleased entries increased ($BASE_COUNT -> $HEAD_COUNT)." + exit 0 + fi + + if [ "$BASE_REF" = "master" ] && [ "$HEAD_REF" = "develop" ]; then + # develop -> master: + # Unreleased must be empty and a new version section must be added. + if [ "$HEAD_COUNT" -ne 0 ]; then + echo "ERROR: develop -> master requires empty Unreleased section." + echo "Current Unreleased entries: $HEAD_COUNT" + exit 1 + fi + + if ! git diff "$BASE_SHA" "$HEAD_SHA" -- CHANGELOG.md | \ + grep -E '^\+\#\# \[[0-9]+\.[0-9]+\.[0-9]+\] - '; then + echo "ERROR: No new version section found in CHANGELOG.md." + exit 1 + fi + + echo "OK: Release changelog structure is valid." + exit 0 + fi + + # For other PR routes, require changelog file change only. + echo "OK: CHANGELOG.md updated for this PR route." \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..27ec440 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: + - develop + - master + - feature/** + pull_request: + branches: + - develop + - master + workflow_dispatch: + inputs: + run_full: + description: Run full pipeline (coverage + full E2E) + required: false + type: boolean + default: false + schedule: + - cron: '0 2 * * *' + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + fast: + name: Fast lane (unit + integration + e2e smoke) + runs-on: ubuntu-latest + if: > + (github.event_name == 'push' && github.ref != 'refs/heads/master') || + (github.event_name == 'pull_request' && github.base_ref != 'master') || + (github.event_name == 'workflow_dispatch' && inputs.run_full != true) + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browser and system deps + run: npx playwright install --with-deps chromium + + - name: Run fast CI lane + run: npm run test:ci:fast + + full: + name: Full lane (coverage + full e2e) + runs-on: ubuntu-latest + if: > + (github.event_name == 'push' && github.ref == 'refs/heads/master') || + (github.event_name == 'pull_request' && github.base_ref == 'master') || + github.event_name == 'schedule' || + (github.event_name == 'workflow_dispatch' && + inputs.run_full == true) + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browser and system deps + run: npx playwright install --with-deps chromium + + - name: Verify Docker availability + run: docker --version + + - name: Run full CI lane + run: npm run test:ci:full diff --git a/.gitignore b/.gitignore index 4a7f73a..551917e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,13 @@ logs .DS_Store .fleet .idea +coverage +coverage/ +tests/e2e/reports +tests/e2e/reports/ # Local env files .env .env.* !.env.example +!tests/e2e/.env.e2e.example diff --git a/.nuxtrc b/.nuxtrc deleted file mode 100644 index 1e1fe83..0000000 --- a/.nuxtrc +++ /dev/null @@ -1 +0,0 @@ -setups.@nuxt/test-utils="4.0.0" \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2d83409 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cucumberautocomplete.steps": [ + "tests/e2e/step-definitions/**/*.mjs", + "tests/e2e/support/**/*.mjs" + ], + "cucumberautocomplete.syncfeatures": "tests/e2e/features/**/*.feature", + "cucumberautocomplete.strictGherkinCompletion": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 84dbcd3..c8254fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Dedicated GitHub Bug issue template at `.github/ISSUE_TEMPLATE/bug.md` + with `bug/` branch guidance and bug-focused planning/test sections +- Chat image messages with inline preview and lightbox support +- E2EE image sending support from chat input +- Media utilities for encrypted media fetch, decrypt, cache, and revoke flows +- Unit and integration test coverage for media utilities and image timelines +- Four-column chat layout with responsive sidebars and mobile overlays +- Space rail with compact/expanded modes and create-space stub route +- Account settings page and space settings base page +- Member list with presence badges (online, away, offline, unknown) +- Presence selector in account settings with homeserver capability check +- Theme preference composable with explicit localStorage persistence +- App i18n composable with English/German locale persistence +- SVG favicon and global head registration +- Message timeline event notices for join/leave/profile and room metadata changes +- Message replies with Matrix `m.in_reply_to` relation payload support +- Reply mode in chat composer with preview and explicit cancel action +- Message interaction bar component for hover/focus actions +- E2E env template and loader for credential-based login scenarios +- Smoke-tagged E2E scenarios and dedicated smoke test script +- GitHub Actions CI workflow with fast and full test lanes +- Synapse-backed E2E orchestration script (`test:e2e`) with startup, seeding, + execution, and teardown flow +- Local Synapse runtime scripts for env loading, server lifecycle management, + and deterministic test seeding +- Synapse Docker Compose setup under `tests/e2e/synapse` for local and CI runs +- Executable chat E2E scenarios for E2EE fallback continuity, image rendering + and lightbox behavior, plus reply and missing-origin fallback handling +- Executable chat E2E presence scenarios for standard member states + (online, away, offline) with sidebar status-dot verification +- Message reactions with add/remove support, grouped emoji counters, and + incremental live updates in timeline rendering +- Advanced reaction emoji picker with category tabs, shortcode conversion + (for example `:wave:` to `👋`), and user-scoped frequent emojis +- Additional reaction coverage in unit and E2E tests for Matrix toggle logic, + aggregation mapping, and message-level reaction UI behavior +- Session restore gating with explicit startup lifecycle state and completion + helpers in `useMatrixClient` +- Global startup loading modal shown while Matrix session restore is in progress +- Global auth middleware for protected routes with restore-aware redirect checks +- Unit coverage for restore-state transitions and global auth middleware +- Root landing page on `/` with hero content, feature cards, and a download + call-to-action placeholder +- Dedicated landing logo background assets (`logoBg.svg`) for the root route +- Unit tests for root-page restore and redirect behavior in + `tests/unit/pages/index.spec.ts` + +### Changed + +- Chat timeline now opens with a bounded initial message window to reduce + startup scroll depth and perceived room-load latency +- Chat history loading switched from button-based pagination to bidirectional + infinite scroll with guarded top/bottom sentinel triggers +- Initial message viewport now centers around the user's last-read event when + newer messages exist, and otherwise starts at the bottom +- Matrix attachment encryption now uses unpadded base64 for IV and hashes +- Image sending flow now checks room encryption before creating media events +- Space navigation now separates icon-button interaction from text-click area +- Room navigation supports home categories for personal/unassigned rooms +- Chat shell styling refined with distinct visual background accents +- Read receipts now show smaller avatars on each reader's last-read message +- README extended with CI and E2E credential setup documentation +- Message list rendering was modularized with a dedicated `ChatMessageItem` + component and reusable `ChatMessageActionBar` +- Full CI lane now runs E2E against local Dockerized Synapse instead of + requiring repository Matrix credential secrets +- Synapse-specific E2E step definitions were consolidated into existing + `login.steps.mjs` and `chat.steps.mjs` +- Synapse environment validation moved from scenario-level Given setup into + hook-based prechecks for seeded-room scenarios +- Message action bar now uses icon-only reply/reaction controls with compact + gray styling and native button tooltips +- Chat page media helper logic was moved into `useChatMedia` to reduce page + size and improve composable reuse +- Root route startup behavior now waits for restore completion before login/chat + navigation decisions +- Root route now keeps restore-aware `watchEffect` gating while rendering + landing content for unauthenticated users +- Landing copy and i18n keys were aligned with the finalized Issue-41 hero and + feature text +- Landing background logo loading now uses a bundler URL import to avoid + runtime path resolution issues + +### Fixed + +- Space-to-room parent mapping now uses Matrix `m.space.parent` state +- Space avatar resolution now supports Matrix mxc avatar URLs with fallback +- CI install issues from unused `@nuxt/test-utils` setup were resolved +- Synapse compose configuration no longer contains duplicate `services` blocks + that broke Docker parsing +- Seeded image events now include upload-backed media URLs so image preview and + lightbox assertions can pass reliably +- Protected route redirects now avoid startup auth flicker by waiting for + session restore and sending unauthenticated users to `/` +- E2EE decryption regressions after login/session changes were fixed by + reusing remembered Matrix `device_id` for the same account and homeserver + while preventing reuse across different homeservers + ## [0.1.0] - 2025-02-13 ### Added diff --git a/README.md b/README.md index d843867..6054acb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Matrix-based chat client with Spaces, Categories, and Rooms. -**Status:** Alpha (v0.1.0) – MVP Phase 1. Full four-column layout follows in Phase 2. +**Status:** Alpha (v0.1.0) – Phase 2 completed, Phase 3 in planning. ## Tech Stack @@ -49,13 +49,68 @@ npm run test:integration # E2E tests (builds app, starts server, runs Cucumber) npm run test:e2e +# E2E smoke tests only (@smoke-tagged scenarios) +npm run test:e2e:smoke + # E2E with visible browser npm run test:e2e:headed +# Coverage (currently from unit Vitest config) +npm run test:coverage + +# CI fast lane (unit + integration + e2e smoke) +npm run test:ci:fast + +# CI full lane (unit + integration + coverage + full e2e) +npm run test:ci:full + # Install Playwright browser (one-time) npm run prepare:e2e ``` +### E2E Credentials + +For credential-based E2E scenarios, create a local env file: + +```bash +cp tests/e2e/.env.e2e.example tests/e2e/.env.e2e.local +``` + +Then fill `E2E_MATRIX_USERNAME` and `E2E_MATRIX_PASSWORD`. +The local file stays untracked. + +## GitHub Actions CI + +This repo uses `.github/workflows/ci.yml` with two lanes: + +- **Fast lane:** runs on push + pull request (`test:ci:fast`). +- **Full lane:** runs nightly and optionally manual + (`test:ci:full`). + +### Required repository secrets (for full lane) + +Set these in GitHub under **Settings > Secrets and variables > Actions**: + +- `E2E_MATRIX_HOMESERVER` (optional, defaults to `https://matrix.org`) +- `E2E_MATRIX_USERNAME` +- `E2E_MATRIX_PASSWORD` + +### How to run manually + +1. Open your repository on GitHub. +2. Go to **Actions**. +3. Select workflow **CI**. +4. Click **Run workflow**. +5. Set `run_full` to `true` if you want the full lane. + +### Note on Nuxt test-utils + +`@nuxt/test-utils` is intentionally not part of the current test runtime. +In our current dependency set, it caused peer dependency conflicts in CI while +its Nuxt runtime helpers were not actively used by our tests. +We can reintroduce it later when we add dedicated Nuxt runtime integration +tests (`setupTest()`, plugin/runtime integration, Nitro route tests). + ## Project Structure ``` @@ -73,9 +128,11 @@ app/ ## Roadmap -- **Phase 1 (current):** Login, Rooms, Chat, Messages +- **Phase 1:** Login, Rooms, Chat, Messages - **Phase 2:** Four-column layout (Spaces | Categories/Rooms | Chat | Members) -- **Phase 3+:** Voice, E2E encryption, Design system, Native clients +- **Phase 3 (current):** Voice, E2E encryption, favorites, and space grouping +- **Phase 4:** Design system and selectable themes +- **Phase 5:** Native clients (Windows, Linux, Android) ## License diff --git a/app/app.vue b/app/app.vue index a0cbd02..82abbc4 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,6 +1,38 @@ + + diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 7c95c6f..f53d934 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1,2 +1,9 @@ @import "tailwindcss"; @import "@nuxt/ui"; + +.decentra-shell { + background-image: + radial-gradient(circle at 12% 8%, rgb(56 189 248 / 12%), transparent 40%), + radial-gradient(circle at 88% 16%, rgb(244 114 182 / 10%), transparent 35%), + linear-gradient(180deg, rgb(15 23 42 / 14%), transparent 28%); +} diff --git a/app/assets/logoBg.svg b/app/assets/logoBg.svg new file mode 100644 index 0000000..9001bea --- /dev/null +++ b/app/assets/logoBg.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/components/Chat/MemberList.vue b/app/components/Chat/MemberList.vue new file mode 100644 index 0000000..e449bf8 --- /dev/null +++ b/app/components/Chat/MemberList.vue @@ -0,0 +1,96 @@ + + + diff --git a/app/components/Chat/MessageActionBar.vue b/app/components/Chat/MessageActionBar.vue new file mode 100644 index 0000000..ff7776b --- /dev/null +++ b/app/components/Chat/MessageActionBar.vue @@ -0,0 +1,105 @@ + + + diff --git a/app/components/Chat/MessageInput.vue b/app/components/Chat/MessageInput.vue index 9f4bd14..30543c7 100644 --- a/app/components/Chat/MessageInput.vue +++ b/app/components/Chat/MessageInput.vue @@ -1,17 +1,30 @@