Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d7f5527
feat(core): add typed build-time feature flags and featureGuard
jhosepmyr Oct 7, 2026
6b43eaa
feat(routing): hide non-MVP routes behind feature flags
jhosepmyr Oct 7, 2026
64c6017
feat(shell): filter the sidebar nav by feature flag
jhosepmyr Oct 7, 2026
fc0e476
feat(shared): drop flagged entries from the command palette and user …
jhosepmyr Oct 7, 2026
1179f04
feat(discovery): hide Jira actions on stories while integrations are off
jhosepmyr Oct 7, 2026
f029545
feat(workspace): hide member-only cards while members are off
jhosepmyr Oct 7, 2026
c565b3f
feat(billing): show the usage page's Manage plan link only with billing
jhosepmyr Oct 7, 2026
1be8be9
docs: document the MVP feature flags
jhosepmyr Oct 7, 2026
78ab854
docs(changelog): note the MVP feature flags
jhosepmyr Oct 7, 2026
903703c
feat(discovery): mix shared meeting audio with the mic in the recorder
jhosepmyr Oct 7, 2026
6809cbb
test(discovery): cover meeting capture, fallbacks and mixing in the r…
jhosepmyr Oct 7, 2026
4a0c26f
feat(discovery): add an in-person / virtual-meeting audio source picker
jhosepmyr Oct 7, 2026
f3f004e
test(discovery): cover the audio source picker and its stored preference
jhosepmyr Oct 7, 2026
d44ba90
feat(discovery): show meeting-audio state and a re-share action in th…
jhosepmyr Oct 7, 2026
18238b4
test(discovery): cover the session bar's meeting-audio badge and re-s…
jhosepmyr Oct 7, 2026
414c2be
docs(changelog): note virtual-meeting audio capture
jhosepmyr Oct 7, 2026
eb395b7
Merge branch 'feature/system-audio-capture' into feature/mvp-ux-polish
jhosepmyr Oct 7, 2026
293d4c3
Merge branch 'feature/mvp-feature-flags' into feature/mvp-ux-polish
jhosepmyr Oct 7, 2026
85e6212
feat(ui): add provenance/review tokens and an AA-safe brand red
jhosepmyr Oct 7, 2026
c03af79
fix(ui): let textareas grow with their content and style span labels
jhosepmyr Oct 7, 2026
dbbc123
feat(i18n): add the copy for the MVP UX polish (es, en)
jhosepmyr Oct 7, 2026
5350bff
feat(discovery): add Gherkin steps and priority, status and origin ba…
jhosepmyr Oct 7, 2026
588454c
feat(discovery): dock the AI review tray to the feed and mark suggest…
jhosepmyr Oct 7, 2026
7256a92
feat(discovery): show what the AI is doing and separate the feed's st…
jhosepmyr Oct 7, 2026
a1586b6
feat(discovery): label the audio source picker and keep the bar's sta…
jhosepmyr Oct 7, 2026
1f98d3b
feat(discovery): make the backlog's order, status and origin explicit
jhosepmyr Oct 7, 2026
5b6fd19
feat(discovery): read-first story detail with Gherkin rows and unsave…
jhosepmyr Oct 7, 2026
26395e3
feat(workspace): group the optional project profile on create and set…
jhosepmyr Oct 7, 2026
2ff4184
feat(workspace): give the projects list a page header
jhosepmyr Oct 7, 2026
c01adbd
docs(screenshots): add the MVP screens for the report
jhosepmyr Oct 7, 2026
19e01e8
docs(changelog): note the MVP UX polish
jhosepmyr Oct 7, 2026
f395279
fix(ui): apply the lighter dark-mode red to text-primary
jhosepmyr Oct 7, 2026
b5b2b47
feat(i18n): add the short tray title and required-field copy
jhosepmyr Oct 7, 2026
7f6d756
feat(discovery): keep the review tray keyboard-friendly and the live …
jhosepmyr Oct 7, 2026
6c1caff
fix(discovery): let the backlog scroll on phones and flag empty story…
jhosepmyr Oct 7, 2026
ec9fa5f
fix(discovery): size the audio source options to their labels on desktop
jhosepmyr Oct 7, 2026
4cb41c5
docs(screenshots): refresh the MVP screens after the follow-up fixes
jhosepmyr Oct 7, 2026
f484246
docs(changelog): note the tray keyboard, focus and mobile backlog fol…
jhosepmyr Oct 7, 2026
ab4ab54
Merge pull request #43 from Kntro-Soft/feature/system-audio-capture
jhosepmyr Oct 7, 2026
b2d2a35
Merge branch 'develop' into feature/mvp-feature-flags
jhosepmyr Oct 7, 2026
2b5267a
Merge pull request #44 from Kntro-Soft/feature/mvp-feature-flags
jhosepmyr Oct 7, 2026
410c4f5
Merge remote-tracking branch 'origin/develop' into feature/mvp-ux-polish
jhosepmyr Oct 7, 2026
c6fdbf0
Merge pull request #45 from Kntro-Soft/feature/mvp-ux-polish
jhosepmyr Oct 7, 2026
3fa7646
ci: deploy main through the reqsai-infra MVP pipeline instead of S3 a…
jhosepmyr Oct 7, 2026
dc09f0e
Merge pull request #46 from Kntro-Soft/ci/deploy-via-infra
jhosepmyr Oct 7, 2026
60eba58
ci(deploy): keep the running API image when the web triggers a deploy
jhosepmyr Oct 7, 2026
cc9c8df
Merge pull request #47 from Kntro-Soft/bugfix/deploy-keep-api-image
jhosepmyr Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 15 additions & 50 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,24 @@ on:
branches: [ main ]
workflow_dispatch:

env:
AWS_REGION: ${{ vars.AWS_REGION }}
S3_BUCKET: ${{ vars.S3_BUCKET }}
CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
permissions:
contents: read

jobs:
deploy:
name: Trigger the MVP deploy
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
timeout-minutes: 5
steps:
- uses: actions/checkout@v7

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build for production
run: bun run build

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Sync static assets to S3 (long cache — hashed filenames)
run: |
aws s3 sync dist/reqsai-web/browser/ s3://${{ env.S3_BUCKET }}/ \
--delete \
--exclude "index.html" \
--exclude "i18n/*" \
--cache-control "public,max-age=31536000,immutable"

- name: Upload index.html and i18n to S3 (no cache — must always be fresh)
run: |
# index.html and the i18n JSON have STABLE, non-fingerprinted names, so they must
# never be served "immutable" — otherwise a browser caches them for a year and never
# picks up new translations after a deploy. Serve them revalidatable instead.
aws s3 cp dist/reqsai-web/browser/index.html \
s3://${{ env.S3_BUCKET }}/index.html \
--cache-control "no-cache,no-store,must-revalidate"
aws s3 cp dist/reqsai-web/browser/i18n/ \
s3://${{ env.S3_BUCKET }}/i18n/ --recursive \
--cache-control "no-cache,must-revalidate"

- name: Invalidate CloudFront cache
- name: Dispatch deploy-mvp in reqsai-infra
env:
GH_TOKEN: ${{ secrets.INFRA_DEPLOY_TOKEN }}
SHA: ${{ github.sha }}
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ env.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
if [ -z "$GH_TOKEN" ]; then
echo "::notice title=Deploy not triggered::INFRA_DEPLOY_TOKEN is not set. Add a fine-grained PAT with Actions read and write on Kntro-Soft/reqsai-infra to deploy main automatically."
exit 0
fi
gh workflow run deploy-mvp.yml --repo Kntro-Soft/reqsai-infra --ref main -f web_ref="$SHA" -f api_ref=keep
echo "Requested the deploy of reqsai-web $SHA: https://github.com/Kntro-Soft/reqsai-infra/actions/workflows/deploy-mvp.yml" >> "$GITHUB_STEP_SUMMARY"
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ _Feature module implementation (iam, billing, workspace, discovery) in progress.

### Added

- **MVP feature flags** (`feature/mvp-feature-flags`): a typed `features` map in both environment
files (`FeatureKey` union + `FeatureFlags.isEnabled()`), all **off** by default, so the deployed
product shows only the MVP. A `featureGuard()` `canMatch` makes a disabled route behave like an
unknown URL (falls through to `/projects`), and the sidebar, command palette, buttons and cards
drop what it hides: `billing` (billing pages, checkout returns, Upgrade CTA), `usage`,
`integrations` (Jira pages + OAuth callback, job banner, import/push actions), `members` (org +
project members, invitation landing, palette action/hits, ownership transfer), `customRoles`
(project roles), and the `notifications` / `tokens` account placeholders. The sidebar also hides
a **Settings** entry with no reachable sub-page. Flipping a flag restores the feature with no
other change; see `docs/FEATURE-FLAGS.md` (incl. the backend's FREE-plan limits).
- **Discovery — virtual-meeting audio capture** (`feature/system-audio-capture`): the analyst can now
record a Zoom / Google Meet / Teams call, not only a face-to-face meeting. A compact **audio-source
picker** next to the record button chooses **In person (microphone)** or **Virtual meeting (microphone +
meeting audio)**, remembered per user in localStorage. The virtual source opens the browser's
screen-share picker straight from the record click (`getDisplayMedia` with tab/window/system audio, voice
processing off and ReqsAI's own tab hidden), drops the unused video track and mixes the shared audio with
the mic in one mono Web Audio mixer — the backend keeps receiving the same 16 kHz Int16 PCM on `/ws/stt`,
and the level meter shows the mix. Sharing without ticking "Share tab audio" warns and lets the user
retry; a dismissed picker stays idle silently; "Stop sharing" mid-session keeps recording the mic with a
notice and a **Share again** action in the session bar; pausing keeps the shared tab, so resuming never
re-opens the picker. The virtual option is disabled (with the reason) outside desktop Chrome/Edge, and a
hint recommends headphones to avoid a duplicated transcript (`discovery.source.*` plus new
`discovery.rec.*` / `discovery.bar.*` keys, en/es).
- **Discovery — live session presence** (`feature/discovery-presence`): the discovery chat now shows who
else is viewing the **live** session — an overlapping avatar stack with a live pulse, a "+N" overflow
bubble and a viewer count, in both the page header and the live session bar. It is fed by a new
Expand Down Expand Up @@ -194,6 +217,55 @@ _Feature module implementation (iam, billing, workspace, discovery) in progress.

### Changed

- **CI — deploy through reqsai-infra** (`ci/deploy-via-infra`): `deploy.yml` no longer syncs to S3 and
invalidates CloudFront; that stack no longer exists, so the old workflow would fail on the next push to
`main`. A push to `main` now asks `Kntro-Soft/reqsai-infra` to run its `deploy-mvp.yml` workflow with
`web_ref` set to the pushed commit; that workflow builds the linux/arm64 nginx image and deploys it to the
single-EC2 MVP host over SSM, and rebuilds reqsai-api from its `main`. Needs the repository secret
`INFRA_DEPLOY_TOKEN` (fine-grained PAT with Actions read and write on `reqsai-infra` only); without it the
job logs a notice and succeeds, so `main` never goes red. Manual runs only dispatch from `main`.
- **UX — MVP usability polish** (`feature/mvp-ux-polish`): a refinement pass on the MVP surfaces driven by
a heuristic evaluation with 6 users (Nielsen, impeccable critique: live session 20 → 26/40, stories
20 → 24/40), keeping the brand, behaviour and copy.
- *Live session*: the AI suggestion queue docks to the feed column as a framed **review tray** ("AI
suggestions to review", counter with prev/next, minimize) instead of floating over the session bar,
header actions and side panel; from `sm` it stops short of the feed bottom so the latest lines stay
visible. ←/→ browse it, Esc minimizes it, focus returns to it after a decision, and focusing a feed
control it covers minimizes it (WCAG 2.4.11). A status line at the live edge says what the AI is doing — listening
(with the last suggestion's age), paused, **processing the final stories after Stop**, or failed with
the backend reason. The transcript recedes into neutral bubbles with visible speaker/time
("Participante n" in Spanish); human decisions become compact "accepted/resolved by the analyst"
rows; accepting confirms that the story landed in the backlog as a draft.
- *AI vs human provenance*: new `ai` / `verified` / `pending` tokens. Everything the AI proposed reads
violet (suggestion cards, generated stories, "IA" origin chip); human-validated content reads emerald;
drafts awaiting review read amber. Priority gets its own glyph scale (critical red, high orange,
medium/low neutral), so red is no longer brand, AI, danger and priority at once.
- *Readable stories*: `hlmInput` textareas grow with their content (they were clipped at a fixed
height); stories read as their sentence and criteria as **Gherkin** steps with a keyword gutter in the
suggestion card, side panel, story detail and create page. The story detail leads with the title,
review status (with what it means) and origin, links an AI story to its capture session, and flags
unsaved story and criterion edits.
- *Backlog order*: sortable headers with a direction arrow and `aria-sort`, a visible "sorted by"
caption, priority re-ordered by meaning on the page (the API sorts the enum alphabetically), origin
and status chips (draft as an amber dashed outline), a page-scrolling chip-row layout below `sm`,
unambiguous dates, an empty state with next steps and retry on load errors. Story forms flag empty
required fields inline.
- *Audio source*: the picker is a labelled segmented control (In person / Virtual meeting) next to a
record button with a record dot and label; the virtual-meeting guidance is a calm two-step note
(share the meeting tab with its audio; wear headphones) and the missing-audio notice offers "Share
again" in place.
- *Project forms*: only the name is required and both forms say so; the optional technical profile is
one group (business context + tech stack, example placeholders, Enter-to-add hint). Settings go from
eight one-field cards to General and Technical profile, and the logo card is titled for the project.
- *Accessibility*: the brand red fill is `#dc2626` so white labels pass AA (4.83:1), red text in dark
uses a lighter step, muted text and the default focus outline meet AA, filter inputs show focus,
story titles are keyboard links, panel tabs/sort chips expose their state, the mobile session-bar
status stays announced, and new suggestions are announced politely. All measured chips are ≥ 4.5:1
in both themes.
- Report screenshots (light, 1440×900, Spanish demo data) in `docs/screenshots/mvp/`. New
`discovery.ai.*`, `discovery.queue.*`, `discovery.suggestion.*`, `stories.statusHint.*`,
`stories.origin.*`, `storyForm.*`, `projectCreate.*`, `projectSettings.*` and `common.optional/required`
keys (EN + ES); `discovery.source.hint` is replaced by `hintShare` / `hintHeadphones`.
- **Core — centralized backend error handling** (`feature/frontend-error-handling`): a shared
`messageForError` helper resolves backend errors by their machine-readable `code` against a single
top-level `errors.<CODE>` i18n block (network / per-status / generic fallback chain). Extended from the
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ export const environment = {
production: false,
apiUrl: '',
wsUrl: '',
features: { billing: false, usage: false, integrations: false, /* … */ } satisfies FeatureFlagMap,
} as const;
```

`features` son los feature flags de build: el MVP oculta billing, usage, integraciones (Jira),
miembros/invitaciones, roles personalizados y los placeholders de cuenta. Ponlos en `true` en
**ambos** archivos para reactivarlos — ver [`docs/FEATURE-FLAGS.md`](docs/FEATURE-FLAGS.md).

---

## Arquitectura
Expand Down Expand Up @@ -130,6 +135,7 @@ Detalle en [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).
|------------------------------------------------------|-----------------------------------------------------|
| [`docs/adr/`](docs/adr/) | Architecture Decision Records (el *por qué*) |
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Docker · AWS S3 + CloudFront · variables de entorno |
| [`docs/FEATURE-FLAGS.md`](docs/FEATURE-FLAGS.md) | Feature flags del MVP y cómo reactivarlos |
| [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md) | Flujo de trabajo, ramas, commits, PR |
| [`CHANGELOG.md`](CHANGELOG.md) | Historial de cambios (Keep a Changelog) |
| [`.github/SECURITY.md`](.github/SECURITY.md) | Política de seguridad y reporte de vulnerabilidades |
Expand Down
74 changes: 74 additions & 0 deletions docs/FEATURE-FLAGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Feature Flags — Reqs-AI Web (Frontend)

The deployed product ships only the **MVP** ("Podar el árbol"): capture a discovery session,
turn it into Gherkin user stories, and ground the AI with the project glossary and constraints.
Everything else stays in the codebase — and in the backend — but is switched off in the UI by a
build-time feature flag. Turning a flag on brings the feature back with **no other code change**.

## Flags

All flags default to `false` in **both** `src/environments/environment.ts` and
`src/environments/environment.prod.ts`.

| Flag | Hides (routes → fall back to `/projects`) | Hides (UI) |
|-----------------|---------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `billing` | `/settings/billing`, `/billing/success`, `/billing/cancel` | Org settings nav entry · user-menu **Upgrade** CTA · Usage page **Manage plan** link |
| `usage` | `/settings/usage` | Org settings nav entry |
| `integrations` | `/settings/integrations`, `/settings/integrations/jira/callback` (Atlassian OAuth), `/projects/:id/settings/integrations` | Org + project settings nav entries · global Jira job banner (and its job polling / STOMP topic) · backlog **Import from Jira**, **Push all to Jira**, **Push to Jira (n)** · story detail **Push to Jira** · the backlog's Jira-target lookup |
| `members` | `/settings/members`, `/projects/:id/settings/members`, `/invitations/accept` (plus the legacy `/members` redirects) | Org + project settings nav entries · project overview **Members** card · command-palette **Members** action and member search hits · org General **Transfer ownership** card (and its member fetch) |
| `customRoles` | `/projects/:id/settings/roles`, `…/roles/new`, `…/roles/:roleId/edit` | Project settings nav entry (and the settings landing / overview "Settings" card no longer count it) |
| `notifications` | `/account/notifications` | Account nav "Soon" placeholder |
| `tokens` | `/account/tokens` | Account nav "Soon" placeholder |

A hidden route does not match at all (`canMatch` returns `false`), so it behaves exactly like an
unknown URL: the router falls through to the wildcard `**` → `/projects` (query params are kept by
that redirect, as for any unknown URL). No blank page, no "no access" toast, and the lazy chunk is
never downloaded.

The sidebar also hides a **Settings** entry when none of its sub-pages is reachable (e.g. an org
admin while `members` and `integrations` are off), and the org-level entry opens the first
reachable sub-page instead of the owner-only General.

### Decisions

- **Invitation links** (`/invitations/accept`) are behind `members`. While it is off nobody can
send an invitation from the UI, and an owner has no screen to see or remove whoever joins, so a
stale invite link must not add people to an org. Turning `members` on restores the flow.
- **Transfer ownership** (org General settings) is behind `members`: without members there is
nobody to pick, and its empty state asks the owner to invite someone.
- The story status filter still lists `EXPORTED`: it is data a story may already carry, not a link.

## Turning a feature on

1. Set the flag to `true` in **both** environment files (the production build uses
`environment.prod.ts` via `fileReplacements`).
2. Rebuild / redeploy. Nothing else changes: routes, nav entries and buttons come back as they were.

Tests override flags with `provideFeatureFlags({ members: true })` in the TestBed providers.

## Adding a flag

1. Add the key to the `FeatureKey` union in `src/app/core/features/feature-flags.ts`; both
environment files then fail to compile until they define it (`satisfies FeatureFlagMap`).
2. Routes: `canMatch: [featureGuard('key')]`. When the route already has a `canMatch` guard, chain
it — `featureGuard('key', requirePermissionMatch('X'))` — because Angular runs every guard of a
`canMatch` array eagerly.
3. Sidebar: add `feature: 'key'` to the item in `src/app/layout/shell/shell-nav.ts`.
4. Anything else (buttons, cards, palette actions, data fetches):
`inject(FeatureFlags).isEnabled('key')`.
5. Extend the URL lists in `src/app/app.routes.spec.ts` and update this table.

## Backend coupling

Flags are **UI-only**: they are not a security boundary, and every backend endpoint stays live
(role/permission checks still apply server-side).

- **Plan limits are enforced by the backend.** Each new organization gets a FREE subscription and
the workspace module rejects work beyond its limits with `422`: 25 projects
(`PROJECT_PLAN_LIMIT_EXCEEDED`), 50 glossary terms per project
(`GLOSSARY_TERM_PLAN_LIMIT_EXCEEDED`), 10 documents per project
(`PROJECT_DOCUMENT_PLAN_LIMIT_EXCEEDED`) and 3 members (`MEMBER_PLAN_LIMIT_EXCEEDED`). The UI shows
the localized error toast; with `billing` off there is no upgrade path, so raise the FREE limits
server-side if the MVP needs more.
- **AI token usage is metered, not enforced**: the backend records consumption against the plan
quota but no MVP flow is blocked by it, and no frontend guard or banner depends on billing.
Binary file added docs/screenshots/mvp/01-proyectos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/mvp/02-nuevo-proyecto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/mvp/03-ajustes-proyecto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/mvp/11-backlog-historias.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/mvp/13-detalle-historia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading