Gap
Every "Open Slack" link in the SPA points at `/chat` or `/chat?channel=`:
- `apps/web/src/screens/Volunteer.tsx:54` — `Open Slack →`
- `apps/web/src/screens/ProjectDetail.tsx:311` — `<Link to={`/chat?channel=${...}`}>`
- `apps/web/src/components/ProjectCard.tsx:76` — same shape
- `apps/web/src/components/AppFooter.tsx:170` — ``
None route anywhere — the SPA doesn't register the route and the API doesn't handle it. Every "Chat" / "Open Slack" link 404s.
Spec
specs/screens/chat.md — the chat route is a 302 redirect to the corresponding codeforphilly.slack.com URL (workspace root for /chat, channel deep-link for ?channel=).
Scope
- Decide host: API (so it survives anywhere the API serves) vs. SPA route (so it can use the client-side router). Probably API since these are pure redirects.
GET /chat → 302 to https://codeforphilly.slack.com/
GET /chat?channel=<name> → 302 to https://codeforphilly.slack.com/messages/<name> (or whichever shape the existing workspace uses)
- Sanitize the channel param to laddr's slack-handle regex (
^[a-z0-9][a-z0-9_-]{0,40}$)
- Frontend
<Link> → <a> audit if SPA routing isn't appropriate for cross-origin redirects
Gap
Every "Open Slack" link in the SPA points at `/chat` or `/chat?channel=`:
None route anywhere — the SPA doesn't register the route and the API doesn't handle it. Every "Chat" / "Open Slack" link 404s.
Spec
specs/screens/chat.md— the chat route is a 302 redirect to the correspondingcodeforphilly.slack.comURL (workspace root for/chat, channel deep-link for?channel=).Scope
GET /chat→ 302 tohttps://codeforphilly.slack.com/GET /chat?channel=<name>→ 302 tohttps://codeforphilly.slack.com/messages/<name>(or whichever shape the existing workspace uses)^[a-z0-9][a-z0-9_-]{0,40}$)<Link>→<a>audit if SPA routing isn't appropriate for cross-origin redirects