docs: My AI Feed design spec + implementation plan#4063
Conversation
…spec Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@rajesh-ms is attempting to deploy a commit to the World Monitor Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis documentation-only PR adds a design spec and a full TDD implementation plan for a new "My AI Feed" panel that aggregates seven curated AI sources (X handles, LinkedIn pages, Anthropic engineering, OpenAI news/engineering, Google DeepMind, AI Engineer YouTube) into every WorldMonitor variant, reusing the existing RSS pipeline.
Confidence Score: 4/5Safe to merge as a docs-only change; no production code is touched. The spec's stale implementation guidance should be corrected before the plan is executed by an agentic worker. The plan is sound and no production code changes. The spec contains stale guidance that directly contradicts the plan on three implementation decisions, posing a real risk during execution if the two documents are not reconciled first. docs/superpowers/specs/2026-05-30-my-ai-feed-design.md needs corrections to §4.2.2, the source count in §2, and the allowlist mirror count in §4.2.5 to stay consistent with the plan. Important Files Changed
Reviews (1): Last reviewed commit: "docs: add My AI Feed implementation plan..." | Re-trigger Greptile |
| ## 2. Goals / Non-Goals | ||
|
|
||
| ### Goals | ||
| - Surface the five sources above in one new panel across every variant. |
There was a problem hiding this comment.
"Five sources" should be "seven sources"
The opening paragraph of §1 explicitly lists 7 sources (X, LinkedIn, Anthropic engineering, OpenAI news, OpenAI engineering, Google DeepMind, AI Engineer YouTube), but the Goals section says "five sources above". An implementer who only skims the spec could treat two of the sources as optional/out-of-scope.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| 2. **`src/config/feeds.ts`** — register the `my-ai-feed` category by importing and | ||
| spreading the list from `my-ai-feed.ts` into FULL_FEEDS and TECH_FEEDS (and via | ||
| `CANONICAL_FEEDS` so finance/commodity/energy/happy resolve it). Add the new | ||
| source `name`s to `SOURCE_TYPES` (type `tech`) and to | ||
| `DEFAULT_ENABLED_SOURCES['my-ai-feed']`. Source `name`s MUST match exactly | ||
| between the feed entries and `DEFAULT_ENABLED_SOURCES` or the panel starts empty. | ||
|
|
||
| 3. **`src/config/panels.ts`** — define the `my-ai-feed` PanelConfig and add it to | ||
| **all six** `*_PANELS` blocks (FULL, TECH, FINANCE, COMMODITY, ENERGY, HAPPY) | ||
| so `VARIANT_DEFAULTS` (= `Object.keys(*_PANELS)`) includes it in every variant. | ||
| Optionally add per-variant name overrides in `VARIANT_PANEL_OVERRIDES`. | ||
|
|
||
| 4. **`src/app/data-loader.ts`** — wire the `my-ai-feed` category into the | ||
| news/content loading path the same way existing feed panels are loaded. | ||
|
|
||
| 5. **Allowlist — update ALL 5 mirrors** (drift causes runtime 403 or CI failure): | ||
| - `shared/rss-allowed-domains.json` | ||
| - `shared/rss-allowed-domains.cjs` | ||
| - `scripts/shared/rss-allowed-domains.json` | ||
| - `api/_rss-allowed-domains.js` | ||
| - `vite.config.ts` → `RSS_PROXY_ALLOWED_DOMAINS` | ||
| Add: the self-hosted RSSHub host, `deepmind.google`, `www.youtube.com`, and |
There was a problem hiding this comment.
Spec §4.2.2 contains three implementation instructions that the plan explicitly overrides
The plan's "Key decisions" section (decisions 1–3) documents that all three instructions below are wrong and must NOT be followed:
- "spreading the list from
my-ai-feed.tsinto FULL_FEEDS and TECH_FEEDS" → plan says CANONICAL-only, never in variant presets. - "Add the new source
names toDEFAULT_ENABLED_SOURCES['my-ai-feed']" → plan says this trips the DEV self-check and is not needed. - "wire the
my-ai-feedcategory into the news/content loading path insrc/app/data-loader.ts" → plan says do NOT touch that file.
An agentic worker executing the spec without first reading the plan would implement all three incorrectly. Consider adding a callout at the top of the spec or striking through the affected instructions so the two documents are unambiguously consistent.
| 5. **Allowlist — update ALL 5 mirrors** (drift causes runtime 403 or CI failure): | ||
| - `shared/rss-allowed-domains.json` | ||
| - `shared/rss-allowed-domains.cjs` | ||
| - `scripts/shared/rss-allowed-domains.json` | ||
| - `api/_rss-allowed-domains.js` | ||
| - `vite.config.ts` → `RSS_PROXY_ALLOWED_DOMAINS` | ||
| Add: the self-hosted RSSHub host, `deepmind.google`, `www.youtube.com`, and | ||
| `youtube.com` (YouTube feed redirects can switch between the two — allowlist both). |
There was a problem hiding this comment.
Spec says 5 allowlist mirrors; plan correctly updates only 4
§4.2.5 lists shared/rss-allowed-domains.cjs as a file to edit, but the plan (Task 4) correctly explains it is module.exports = require('./rss-allowed-domains.json') — a passthrough that auto-reflects the JSON. Editing it would create an inconsistency instead of fixing one. The spec should note that this file requires no change.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| These are read at **build time** by Vite (the `VITE_` prefix), so set them in | ||
| the Vercel project (Production + Preview) and in local `.env`, then redeploy: | ||
|
|
||
| | Env var | Example | Notes | | ||
| |---|---|---| | ||
| | `VITE_RSSHUB_BASE` | `https://rsshub.example.com` | Public HTTPS base, no trailing slash needed. Unset ⇒ only native sources appear. | | ||
| | `VITE_AI_X_HANDLES` | `swyx,karpathy,AnthropicAI` | Comma-separated, no `@`. Keep to ~3-5 to limit fan-out. | | ||
| | `VITE_AI_LINKEDIN_PAGES` | `anthropic,openai` | Comma-separated company slugs. | | ||
|
|
There was a problem hiding this comment.
VITE_ prefix embeds X handles and LinkedIn slugs in the client bundle
Vite statically replaces import.meta.env.VITE_* references at build time and includes the values verbatim in the public JS bundle. VITE_RSSHUB_BASE, VITE_AI_X_HANDLES, and VITE_AI_LINKEDIN_PAGES are therefore readable by anyone who inspects the downloaded assets — they expose the full RSSHub instance URL, every tracked X handle, and every LinkedIn slug. The runbook should explicitly mention that these values will be visible in dist/*.js and that operators should not use this mechanism if they want the monitored account list to remain private. Network-level access controls on the RSSHub host are advisable regardless of whether an ACCESS_KEY is set.
| it('registers my-ai-feed as a CANONICAL-only category with rss-wrapped urls', () => { | ||
| assert.match( | ||
| feedsSrc, | ||
| /'my-ai-feed':\s*buildMyAiFeeds\(\)\.map\(\(f\) => \(\{ \.\.\.f, url: rss\(f\.url as string\) \}\)\)/, | ||
| ); | ||
| }); | ||
|
|
||
| it('does NOT add my-ai-feed to any variant FEEDS preset', () => { | ||
| // CANONICAL-only: the only my-ai-feed key lives in the mergeCanonicalFeeds([...]) call. | ||
| const occurrences = feedsSrc.match(/'my-ai-feed':/g) ?? []; | ||
| assert.equal(occurrences.length, 1, 'my-ai-feed should appear exactly once (CANONICAL map only)'); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Registration test regex is format-sensitive and will break on minor reformatting
The pattern requires the .map() call to be on a single line with exact spacing. If Prettier wraps it across lines the regex will fail. Consider splitting into two independent assertions — one for 'my-ai-feed': and one for buildMyAiFeeds() — or using a multiline-aware pattern with [\s\S]*? between the key fragments.
…sources) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tegory) Adds a panel:my-ai-feed command in commands.ts and registers my-ai-feed in PANEL_CATEGORY_MAP (topical + techAi) so it satisfies the panel-config discoverability guardrails. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…re OpenAI Adds end-to-end Azure Container Apps deployment for the self-hosted Docker image (web app + redis-rest shim + Azure Cache for Redis) and the fixes needed to make it actually serve traffic: - infra/: Bicep for ACR, Container Apps env, web + redis-rest apps, Azure Cache for Redis, Log Analytics, and AcrPull role assignments; azure.yaml azd service map (both services language: docker). - SSRF guard: in docker mode the sidecar now auto-allowlists the operator- configured UPSTASH_REDIS_REST_URL origin so the web app can reach the internal redis-rest shim (which resolves to a private IP). Gated to docker mode only; desktop/cloud startups keep private fetches blocked. Covered by two new regression tests. - CRLF: normalize docker/entrypoint.sh to LF (Dockerfile sed + .gitattributes) so the sh shebang works on Linux containers. - redis-rest shim: harden node-redis client with keepAlive, reconnectStrategy, and pingInterval to survive Azure Redis idle disconnects. - _upstash-json: log redisPipeline HTTP/fetch failures instead of swallowing them silently. - llm.ts: send api-key header for Azure OpenAI (*.openai.azure.com) so the analysis panels can use an Azure OpenAI deployment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure OpenAI resources with key-based auth disabled require an Entra ID bearer token instead of an api-key. Adds an OAuth2 client-credentials flow (scope https://cognitiveservices.azure.com/.default) with token caching and in-flight de-duplication in a new llm-azure-auth module. getProviderCredentials now exposes an optional async authHeaderProvider; when AZURE_OPENAI_TENANT_ID/CLIENT_ID/CLIENT_SECRET are set for an *.openai.azure.com endpoint, it resolves a fresh Bearer token at call time. The three LLM fetch sites (callLlm, callLlmReasoningStream, summarize-article) merge it before the request. API-key auth remains the fallback when no Entra creds are present. Infra: plumb the service-principal env vars through main.bicep -> container-app-web.bicep (client secret stored as a Container App secret). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure OpenAI reasoning models (gpt-5.x, o-series) reject the legacy max_tokens body field and require max_completion_tokens. Add a maxTokensParam to ProviderCredentials, defaulting Azure OpenAI endpoints to max_completion_tokens (override via LLM_MAX_TOKENS_PARAM), and emit it at all three LLM call sites. Also plumb WM_SESSION_SECRET through the web container-app bicep so anonymous browser session tokens (required for tier-gated analysis RPCs) work after a full provision. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds the design spec and a full TDD implementation plan for a new "My AI Feed" (
my-ai-feed) news panel that aggregates seven curated AI sources into one panel shown in every variant. This PR is documentation only - no production code changes yet - so the approach can be reviewed before any implementation lands.The motivation: surface a curated stream of high-signal AI sources (the accounts/orgs the user actually follows) directly in WorldMonitor, reusing the existing RSS pipeline rather than building a new subsystem.
The seven sources and how each is fetched:
Key design decisions (verified against the codebase)
my-ai-feedis registered inCANONICAL_FEEDSbut intentionally left out of every variant''sFEEDSpreset.resolveNewsCategories()then treats it asisCustomeverywhere, so the client fetches its full set directly and the per-variant server digest needs zero changes.DEFAULT_ENABLED_SOURCESentry. Source enablement is computed only fromFULL_FEEDS, so CANONICAL-only sources are enabled by default automatically; adding them would trip the DEV self-check. This also handles the dynamic X/LinkedIn source names that cannot be statically enumerated.buildMyAiFeeds().map(...)), notrss(...)literals, so the feed validator and client/server parity tests stay green and credentials never land in client-visible URLs.src/config/my-ai-feed.tsis designed to be unit-testable under thetsxrunner (guardedimport.meta.envaccess + injectable env), avoiding the knownimport.meta.envlandmine infeeds.ts/proxy.ts.The plan covers 6 bite-sized TDD tasks (builder + tests, feeds.ts registration, panels in all 6 variants, allowlist mirrors, validation) plus a self-host RSSHub runbook (Docker, build-time env vars, allowlist host, route verification, YouTube channel-id resolution).
Type of change
Affected areas
/api/*)Checklist
api/rss-proxy.jsallowlist (if adding feeds)Screenshots
N/A - documentation only.