Publish radar to Pulse — single slug, two share toggles#171
Merged
Conversation
First of two coordinated PRs to add "share your radar on Pulse" alongside the existing portfolio sharing. This PR is the Quantic side: schema, NATS publishing, Settings UI. Pulse side (consumer + worker + LiveView + dashboard aggregates) comes next. Sharing model: one slug per user (the existing `portfolio_slug`) with two independent booleans `share_portfolio` (default true, back-compat) and `share_radar` (default false, opt-in). When both are set, the user gets `pulse.quantic.es/p/<slug>` AND `pulse.quantic.es/r/<slug>`. Publish flow mirrors the portfolio pipeline: - `User#publish_pulse_changes` (renamed from publish_portfolio_slug_change) detects all transitions across slug + the two toggles and emits the right opted_in / opted_out events per surface. Six cases covered in spec. - `RadarStock#after_commit :publish_radar_updated` re-emits `radar.updated` on any radar change (add / remove / target_price edit) — same pattern as `Holding#publish_portfolio_updated`. - New `RadarPayloadBuilder` builds a v1 payload with enough stock metadata (price, dividend_yield, 52w range, MA200) that Pulse can render a standalone radar page without needing the portfolio events. Settings UI: existing "Share on Pulse" card grows two checkbox rows inside the slug field. New `ShareToggle` component renders each toggle with a description and (when enabled) the live public URL. Pulse-side preview: Pulse already ignores unknown NATS subjects gracefully (Consumer.ex line 126), so deploying this Quantic PR first is safe — `radar.*` events fire but Pulse just logs warnings until PR 2 ships. Specs: 22 new examples covering the payload builder, all 6 publish transitions, RadarStock callback gating, and the controller's new fields. Full suite 676 examples / 0 failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9 tasks
Three small frontend fixes on the radar surface to address feedback during the share-radar-on-pulse round: 1. **Localize stock sectors**. New `translateSector(t, sector)` helper in `lib/sectors.ts` plus a `sectors` namespace in both locales covering the 17 GICS / Yahoo Finance names + `Unknown`. Unknown sectors pass through verbatim so a new provider sector never blanks the UI. Wired into `PortfolioStatsCard`'s sector legend and tooltip; matches the equivalent helper that just landed in Pulse for visual parity across both apps. 2. **Share-on-Pulse button on the radar page**. `PulseShareButton` is now parameterised with a `kind: 'portfolio' | 'radar'` prop — gates on the matching toggle (`sharePortfolio` / `shareRadar`) and links to the right URL. RadarPage gets the button under the title (gated on `radarStocks.length > 0`); PortfolioPage now passes `kind="portfolio"` explicitly. Also fixed the gate: if a slug exists but the relevant toggle is off, show the "opt-in from Settings" hint instead of a dead public link. 3. **Radar AI insights title**. `insights.title` was used by both `PortfolioInsights` and `RadarInsights` but said "AI Portfolio Insights" in both languages. Split into `insights.portfolioTitle` and `insights.radarTitle` so each component renders correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of two coordinated PRs to add "share your radar on Pulse" alongside the existing portfolio sharing. This PR is the Quantic side (schema + NATS publishing + Settings UI). Pulse-side consumer + worker + LiveView + dashboard aggregates ship next as a separate PR.
Sharing model
One slug per user (existing
portfolio_slug) with two booleans:share_portfolio— defaulttrue(back-compat, current sharers keep sharing)share_radar— defaultfalse(opt-in for the new surface)When both are enabled:
pulse.quantic.es/p/<slug>ANDpulse.quantic.es/r/<slug>.Publish flow (mirrors portfolio path)
User#publish_pulse_changes(renamed frompublish_portfolio_slug_change) — fires on save when slug, share_portfolio, or share_radar changed. Diffs the effective shared state per surface and emits{portfolio,radar}.opted_in/outcorrectly across all 6 transitions.RadarStock#after_commit :publish_radar_updated— re-emitsradar.updatedon any radar change (add / remove / target_price edit). Mirror ofHolding#publish_portfolio_updated.RadarPayloadBuilder(new) — v1 payload with enough stock metadata (price, dividend_yield, 52w range, MA200) that Pulse can render a standalone radar page without portfolio events.Settings UI
Existing Share on Pulse card gets a "What to share publicly" subsection (visible only when a slug is set) with two checkbox rows. New
ShareTogglecomponent shows the live public URL per surface when enabled.Frontend polish (added after initial scope)
translateSector(t, sector)helper +sectorsnamespace in bothen.ts/es.ts. Covers the 17 GICS / Yahoo Finance names +Unknown; unknown sectors pass through verbatim. Wired intoPortfolioStatsCard's sector legend and tooltip.PulseShareButtonparameterised withkind: 'portfolio' | 'radar'— gates on the matching toggle (sharePortfolio/shareRadar) and links to the right URL. Also fixed: if a slug exists but the relevant toggle is off, show the "opt in from Settings" hint instead of a dead public link.RadarInsightsandPortfolioInsightsshared theinsights.titlekey. Split intoinsights.portfolioTitleandinsights.radarTitle.Deploy order: ship the Pulse PR first
NATS is plain pub/sub (no JetStream queue) — messages sent to a subject with no live subscriber are dropped at the broker. If this PR deploys before fleveque/pulse#35:
radar.opted_inevents for users toggling Share Radar are dropped — their radar never registers in Pulse until they next edit it.radar.updatedevents from price refreshes are also dropped during the window.Order to ship: fleveque/pulse#35 first (Pulse starts subscribing to
radar.*and idles harmlessly with no publishers), then this PR (Quantic starts publishing into a Pulse that's ready to consume from event #1).Test plan
bundle exec rspec— 676 examples / 0 failures (22 new across payload builder, user transitions, RadarStock callback, profile controller)bin/rubocopon touched Ruby — cleannpx tsc --noEmit— cleannpx vite build— succeedslocalhost:8222)radar.updatedfiresRollback
Single revert. Migration adds two columns with safe defaults — leaving them in place is harmless if code reverts.
Sister PR
Pulse-side (RadarWorker + RadarLive + Dashboard "Community watchlist"): fleveque/pulse#35.
🤖 Generated with Claude Code