# AGENTS.md — <%= projectName %>

Angular 19 frontend for the Alberta Digital Service Platform (ADSP).
Generated by `nx g @abgov/nx-adsp:angular-app`.
<% if (pairedProject) { %>
## Paired service

This app is paired with **`<%= pairedProject %>`** — the Express backend it talks to.
API calls use the `/api/` prefix which the Angular dev proxy rewrites to `/<%= pairedProject %>/` on port 3333.
When working on a feature that spans both projects, read `apps/<%= pairedProject %>/AGENTS.md` for the service context.
<% } %>
## Running Nx commands (coding agents)

Run generators with `--no-interactive` **and** every required option supplied. With
`--no-interactive`, a missing required option errors instead of prompting, so an
interactive prompt never blocks your session (`CI=true` in the env does the same and
also skips the Nx Cloud prompt). This applies to `nx g` generators; `nx run <target>`
executors read options from `project.json` and do not prompt.

## Stack


- **UI**: Angular 19 standalone + GoA design system (`@abgov/angular-components` — `Goab*` components)
- **Auth**: `keycloak-angular` with `keycloak-js`
- **Router**: Angular Router with `createAuthGuard` from `keycloak-angular`
- **Tests**: Jest

## Key files

| File | Purpose |
|------|---------|
| `src/main.ts` | Entry — bootstraps `AppComponent` with `appConfig`, imports `zone.js` |
| `src/app/app.config.ts` | `provideKeycloak` (PKCE, silent SSO), `provideRouter`, `provideHttpClient` |
| `src/app/app.component.ts` | Shell — auth state, hero banner background workaround, public API call |
| `src/app/app.routes.ts` | Routes — `/protected` with `createAuthGuard` |
| `src/app/protected/protected.component.ts` | Protected route — shows authenticated user info |
| `src/environments/environment.ts` | Access URL, realm, client ID — pre-set from ADSP tenant |

## Auth pattern

```typescript
import Keycloak from 'keycloak-js';
import { inject } from '@angular/core';

private keycloak = inject(Keycloak); // provided by provideKeycloak in app.config.ts

this.keycloak.authenticated          // boolean
this.keycloak.tokenParsed?.['name']  // user display name
this.keycloak.login()
this.keycloak.logout({ redirectUri: window.location.origin })
```

Route guard using `createAuthGuard` from `keycloak-angular`:

```typescript
const authGuard = createAuthGuard(async (_route, _state, { authenticated, keycloak }) => {
  if (authenticated) return true;
  await keycloak.login({ redirectUri: window.location.href });
  return false;
});
```

## GoA design system

**Find a component:** browse the gallery at
[design.alberta.ca/components](https://design.alberta.ca/components/) — press ⌘K to
search components and examples. Each component's page documents its properties,
events, and usage; treat it as the source of truth. Angular uses the `Goab*`
wrappers from `@abgov/angular-components`, imported into each standalone component's
`imports` array:

```typescript
import { GoabButton, GoabAppHeader } from '@abgov/angular-components';

@Component({ imports: [GoabButton, GoabAppHeader], ... })
```

**Events**: the Angular wrappers expose events as outputs — bind with `(onClick)`,
`(onChange)`, etc. on the `goab-*` element:

```html
<goab-button type="tertiary" (onClick)="login()">Sign in</goab-button>
<goab-dropdown (onChange)="handleSelect($event)"></goab-dropdown>
```

**Known limitation:** `GoabHeroBanner.backgroundUrl` does not bind reliably via
Angular's template binding due to an `@if(isReady)` timing issue inside the
component wrapper. Use `MutationObserver` to set the property directly after
the inner `<goa-hero-banner>` element appears — see `app.component.ts` for the
established pattern.

## Adding a new route

1. Create `src/app/my-feature/my-feature.component.ts` as a standalone component
2. Add the route to `src/app/app.routes.ts`:
   ```typescript
   { path: 'my-feature', component: MyFeatureComponent }
   // For an authenticated route:
   { path: 'my-feature', component: MyFeatureComponent, canActivate: [authGuard] }
   ```
3. Add required `Goab*` components to the new component's `imports` array

## Backend API calls (proxy setup)

`includeBearerTokenInterceptor` (configured in `app.config.ts`) automatically
attaches the Keycloak access token to all outgoing HTTP requests.
Use relative `/api/` paths — they route through the Angular dev proxy and nginx in production:

```typescript
// ✓ correct — works through proxy in dev, nginx in production
this.http.get('/api/v1/my-resource')

// ✗ wrong — bypasses proxy, won't work in production
this.http.get('http://localhost:3333/my-service/v1/my-resource')
```

## Testing

Tests live alongside source files (`*.spec.ts`) and run with Jest:

```bash
nx test <%= projectName %>          # run all tests
nx test <%= projectName %> --watch  # watch mode
```

Component test example:

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyFeatureComponent } from './my-feature.component';

describe('MyFeatureComponent', () => {
  let fixture: ComponentFixture<MyFeatureComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MyFeatureComponent],
    }).compileComponents();
    fixture = TestBed.createComponent(MyFeatureComponent);
  });

  it('renders', () => {
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Hello');
  });
});
```

## OpenShift targets

```bash
nx run <%= projectName %>:sandbox           # build locally (podman) + push to GHCR + deploy
nx run <%= projectName %>:sandbox-teardown  # remove sandbox resources + delete the GHCR image
nx run <%= projectName %>:apply-envs        # apply manifests to all environments
nx run <%= projectName %>:teardown-dev      # remove from dev environment
```

## What NOT to change

- `app.config.ts` — `provideKeycloak` initialises keycloak-js once; do not
  create a second Keycloak instance elsewhere
- `silent-check-sso.html` — served from `public/`; required for keycloak-js
  silent SSO check on page load
- `environments/environment.ts` — access URL and realm are pre-configured for
  the ADSP tenant; override at runtime via environment variables

## Sandbox deployment (local build)

**Deployment target: `sandbox`.** `nx run <%= projectName %>:sandbox` builds the image **locally with podman**, pushes it to GHCR, and deploys it to your namespace — no git push or CI wait. Run the generator once first to add the targets:

```bash
nx g @abgov/nx-oc:sandbox <%= projectName %> --sandboxProject <your-namespace>
```

That also writes **`.openshift/<%= projectName %>/SANDBOX.md`** — the full deploy runbook: prerequisites (`podman`, `oc` login, a `gh` account with **`write:packages`** as the *active* `gh` account), preflight failures and their fixes, `--skipBuild`/`--skipPush` to resume a partial deploy, a copy-paste manual-completion sequence, and troubleshooting (CPU quota, `CrashLoopBackOff`, registry auth, redirect URIs). Read it whenever a deploy misbehaves.
