Rebrand/miez UI - #55
Conversation
Replace the blue brand primary (reads as a party colour) with a neutral warm-graphite ink, and introduce the Miez camp system: - --camp-a (reformist, teal), --camp-b (suveranist, terracotta), --core (neutral sand/gold), each with -fg and -surface variants, in :root + .dark and exposed as Tailwind utilities via @theme inline. - Camps tuned to near-equal lightness/chroma so neither reads as louder. - Rename theme storage key biviant-theme-preference -> miez-theme-preference (constant + e2e spec). - design-system.test: assert camp tokens exist and stay perceptually symmetric. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Single source packages/i18n/brand.ts: BRAND_NAME = "Miez" (cascades to all interpolated web + native strings, titles, SEO strings). - BrandLogo: replace dual-PNG lockup with an inline-SVG wordmark in currentColor (theme-adaptive, no image swap); the "e" is the sliced-disc core motif (ring + centre dot). Drop old logo-biviant*.png. - Add public/favicon.svg (core mark) + public/manifest.webmanifest (name/short_name/theme_color/icons/lang=ro); wire both into <head>, update theme-color to the new dark base. - Rename cosmetic literals: data-export filename, feed recent-search storage key, JSON-LD logo path. - Native: app.json display name -> Miez (bundle IDs + SecureStore biviant.* keys deliberately retained; see docs/MIEZ_REBRAND.md). - docs/MIEZ_REBRAND.md: raster-asset TODOs + intentionally-retained identifiers (BiviantBot crawler token, storage keys, bundle IDs, domains -> MIEZ-9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-layout EventDetailTabs to the Miez model, current data only, no new API:
- Promote the neutral summary to a "Miezul" core block up top (core-token
tinted); falls back to the pending state with a TODO(backend) for a
dedicated common-summary field.
- Split reformist/suveranist into two equal-weight camp-tinted crusts
("Coaja reformistă" / "Coaja suveranistă"): side by side on desktop
(grid), tab-switched on mobile — single DOM (forceMount) so both stay
crawlable. Reformist takes the fixed left slot (documented arbitrary axis
order); mobile opens on a per-session-random crust so neither is default.
- Respect v7: a non-diverging side stays hidden; a lone crust renders full
width. CASE D (perspectiveApplicable=false) keeps the no-axis note.
- Sources stay shared below both crusts (API has no per-perspective source
attribution — per-crust lists would fabricate a camp→source mapping;
TODO(backend) noted). Global impact demoted to the bottom (MIEZ-4).
- Add event.core / event.crustReformist / event.crustSuveranist strings.
- Rewrite the component test for the new structure (8 passing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace the always-open "Ce înseamnă asta" section with a collapsed-by- default native <details> accordion labelled "Context global", below the crusts and off the initial viewport. No backend change. - Add a guarded captureEvent() helper on the PostHog singleton; fire "global_impact_expand" (with eventId) once per expand so there's data to justify killing or fixing the section later. - Add event.globalContext string; test asserts closed-on-load + expand event. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralize the Miez vocabulary in packages/i18n (ro + en): - Rework SEO title/description to "fiecare știre, din ambele tabere" — drop the old "analizată fapt cu fapt" tagline; no "adevărul"/"obiectiv" self-description. - feed.none -> "Nicio știre nouă. Miezul se coace."; feed.loading -> "Tăiem coaja…". - Add axis.reformist/suveranist/tooltip/methodologyCta (MIEZ-6), onboarding.valueLine (MIEZ-8), share.eventText (share sheet), and the MIEZ-7 footer.methodology/funding labels. - Share sheet now uses "Vezi miezul acestei știri:" + URL; drop the unused summary prop from ShareEventButton and its call site. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add /metodologie (axis placement is per-outlet not per-article, orientation separate from reliability, axis calculated not decreed, how to contest a placement) and /finantare (funding transparency; no invented business details, real specifics left as a marked TODO per FOOTER_TODO convention). - Both use the shared StaticPage components, are indexable (staticPageHead, no noindex), and are linked from the footer (methodology under Resources, funding under About). The methodology is also linked from the axis explanation under sources (MIEZ-6). - Regenerate routeTree.gen.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- BiasIndicator becomes the camp axis: camp-a (reformist) → --core centre zone → camp-b (suveranist) gradient, the source dot coloured by camp/core, and equal-type end labels "Reformist"/"Suveranist" tinted with the matched camp -fg tokens. Symmetric by construction (reformist pinned left since bias runs -5→+5; documented as an arbitrary fixed choice). Renders at list-item (sm) and detail (lg) sizes; track carries the axis.tooltip title. - SourceCoverageSummary shows the axis explanation + "Vezi metodologia" link once under the axes (only when sources exist), wiring the tooltip to /metodologie. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add MiezOnboarding: a single dismissible screen shown exactly once per
device (localStorage miez-onboarding-v1), rendered at the root. The
three-beat pitch ("Presa s-a rupt în două tabere" / "Miez îți arată ambele"
/ "Gratuit, fără cont") over the sliced-disc motif (two camps + core),
CTA "Vezi miezul zilei" into the feed. Skippable (skip button, backdrop,
Escape); no account prompt anywhere. Renders nothing on SSR / for returning
devices, so no flash.
- Fire onboarding_dismiss with the dismissal reason.
- Add onboarding.miez.* strings (ro + en). Test covers show-once, dismiss,
CTA navigation, and the no-account guarantee.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- seo.ts: canonical/og host -> https://www.miez.news (og:image too); JSON-LD org becomes NewsMediaOrganization (name/logo already Miez). og:site_name is Miez via BRAND_NAME. - Story <title> pattern -> "«story» | Miez". - sitemap.ts default host -> www.miez.news. - Event share card (shareAssetsNode.ts): drop the blue "B" mark/BIVIANT wordmark for the Miez core mark (gold ring + dot) + "MIEZ", gold accents instead of party-blue, tagline "Every story, from both camps", footer "Shared from miez.news". - Native lib/site.ts SITE_URL -> miez.news (+ test, comments). - Email/auth infra domains intentionally deferred to the infra cutover (needs DNS + email verification + auth origins); documented in docs/MIEZ_REBRAND.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Land the real Miez mark: logo-mark.png (512, teal/coral disc + core) and apple-touch-icon.png (180). BrandLogo now renders the PNG mark + "miez" wordmark (wordmark hidden on mobile in the header, mark-only); apple-touch points at the dedicated icon. - manifest.webmanifest: add id, categories:["news"], and the 512 PNG icon alongside the SVG so the PWA install prompt has a real raster icon. - docs/MIEZ_REBRAND.md: record the store-listing text (title "Miez – știri din ambele tabere", short description, 4 screenshots, adaptive icon — managed in App Store Connect / Play Console, no in-repo files) and the remaining raster TODOs (og-image verify, PWA screenshots, native icons). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add docs/MIEZ_QA.md — the signed-off checklist for the Biviant → Miez UI rebrand. Automated gates green: web tsc + 84 tests + production build, backend 272, native 39, design-system symmetry/dark-parity enforcement. Leftover-Biviant grep clean except the documented BiviantBot crawler token. Records neutrality/diacritics/dark-mode checks and the known follow-ups (crawler rename, email/auth domain cutover, raster TODOs, backend TODOs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Miezul-core + two-crust layout didn't land visually. Restore the original perspective tabs (reformist / centre / suveranist) with their exact styling — underline triggers, forceMount panels, AI disclosure placement, CASE D note — and only relabel the centre "Neutră" tab to "Miezul" (event.core). The MIEZ-4 global-impact accordion is kept. Test restored to the 3-tab behaviour + the accordion case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restore the plain "Ce înseamnă asta" (event.meaning) global-impact section instead of the collapsed "Context global" <details> accordion; drop the expand analytics + unused captureEvent import. Test updated to assert the section renders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The feed event-card distribution bar (and the source-coverage bar) render from the --bias-* tokens, which were still the old indigo/amber. Repoint them to the reformist–suveranist camp palette: left = camp-a teal, right = camp-b terracotta, centre = neutral core (light + dark), kept near-equal L/C so neither side reads louder. Update the design-system test description. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a general contact form on /contact that persists messages for admins and emails the operators on arrival. - Schema: contactMessages table (name/email/subject/message/status) indexed by_status_createdAt. - convex/contact.ts: submitContactMessage (public, validated, rate-limited per-sender + global), listContactMessagesForAdmin + markContactMessageHandled (admin-guarded). - convex/emails.ts: sendContactMessageEmail internal action → ADMIN_EMAILS, reply-to set to the sender so admins answer directly. - /admin/contact dashboard route: read messages, mark handled, quick reply. - ContactForm component wired into the /contact page (no account needed). - Tests: contact.test.ts (store + schedule email + validation + rate limit); footer-pages.test stubs useMutation so the page still renders provider-less. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 22 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (26)
WalkthroughThe PR rebrands the applications from Biviant to Miez, adds onboarding and contact workflows, introduces funding and methodology pages, updates camp-axis presentation, and revises web metadata, sharing, localization, and design tokens. ChangesMiez product update
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/components/contact-form.tsx`:
- Around line 1-131: Replace the hardcoded user-facing Romanian strings in
ContactForm, including validation toasts, labels, button states, and success
confirmation text, with translations obtained through the app’s established
useT() i18n hook. Add the hook to the component and use translation keys for
every rendered or toasted message while preserving the existing form behavior.
- Around line 63-79: Update the confirmation container returned by the sent
state in the contact form to include an appropriate ARIA live-region attribute
and status role, ensuring screen readers announce the successful submission
while preserving the existing content and behavior.
In `@apps/web/src/components/MiezOnboarding.test.tsx`:
- Around line 50-52: Update the text assertions in the onboarding dialog test to
use case-insensitive regular expressions with word boundaries instead of bare
toContain checks. Ensure “cont” matches as a standalone word regardless of
punctuation or spacing, and “sign” does not match words such as “design” or
“signal”; preserve the existing prohibition on “log in.”
In `@apps/web/src/components/MiezOnboarding.tsx`:
- Around line 59-72: Add a JavaScript focus trap to the outer dialog in
MiezOnboarding, using a modal ref and the existing onKeyDown handler. Keep focus
cycling between the dialog’s first and last focusable elements when Tab or
Shift+Tab reaches either boundary, and attach the ref to the element with
role="dialog" so keyboard focus cannot escape the onboarding modal.
In `@apps/web/src/index.css`:
- Line 143: Update the inline comment on the --bias-right CSS variable to
include whitespace before the closing comment delimiter, preserving the variable
value and existing comment text.
In `@apps/web/src/routes/__root.tsx`:
- Line 114: Update the theme-color metadata in the root document head to use the
actual light and dark background token values instead of the hard-coded `#17181c`.
Add separate prefers-color-scheme-scoped entries so browser chrome follows the
active light or dark theme, while preserving the existing metadata structure.
In `@apps/web/src/routes/contact.tsx`:
- Line 3: Update ContactForm so every input and textarea has an appropriate
aria-label, and mark the successful-submit status message with the required role
and aria-live attributes. Preserve the existing form behavior and message
content while ensuring assistive technology announces both fields and submission
results.
In `@packages/backend/convex/contact.ts`:
- Around line 117-118: Update requireAdminUser in lib/betaAccess to return a
type that explicitly includes the administrator’s email, preserving its existing
authentication and authorization behavior. Then update the handledByEmail
assignment in the contact flow to access admin.email directly without a type
assertion, while retaining appropriate null/undefined handling if the helper
permits it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2a24921a-a7fe-4342-9690-6ec99613de60
⛔ Files ignored due to path filters (7)
apps/web/public/apple-touch-icon.pngis excluded by!**/*.pngapps/web/public/favicon.svgis excluded by!**/*.svgapps/web/public/logo-biviant-dark.pngis excluded by!**/*.pngapps/web/public/logo-biviant.pngis excluded by!**/*.pngapps/web/public/logo-mark.pngis excluded by!**/*.pngapps/web/src/routeTree.gen.tsis excluded by!**/routeTree.gen.tspackages/backend/convex/_generated/api.d.tsis excluded by!**/_generated/**,!**/_generated/**
📒 Files selected for processing (40)
apps/native/app.jsonapps/native/lib/site.test.tsapps/native/lib/site.tsapps/web/e2e/theme.spec.tsapps/web/public/manifest.webmanifestapps/web/src/components/BrandLogo.tsxapps/web/src/components/MiezOnboarding.test.tsxapps/web/src/components/MiezOnboarding.tsxapps/web/src/components/bias-indicator.tsxapps/web/src/components/contact-form.tsxapps/web/src/components/feed/event-detail-tabs.test.tsxapps/web/src/components/feed/event-detail-tabs.tsxapps/web/src/components/feed/source-coverage-summary.tsxapps/web/src/components/header.tsxapps/web/src/components/layout/Footer.tsxapps/web/src/components/profile/AuthenticatedProfile.tsxapps/web/src/components/share-event-button.tsxapps/web/src/design-system.test.tsapps/web/src/index.cssapps/web/src/lib/posthog.tsxapps/web/src/lib/seo.tsapps/web/src/lib/theme/index.tsapps/web/src/routes/__root.tsxapps/web/src/routes/admin.contact.tsxapps/web/src/routes/contact.tsxapps/web/src/routes/event.$slug.tsxapps/web/src/routes/feed.tsxapps/web/src/routes/finantare.tsxapps/web/src/routes/footer-pages.test.tsxapps/web/src/routes/metodologie.tsxdocs/MIEZ_QA.mddocs/MIEZ_REBRAND.mdpackages/backend/convex/contact.test.tspackages/backend/convex/contact.tspackages/backend/convex/emails.tspackages/backend/convex/schema.tspackages/backend/convex/shareAssetsNode.tspackages/backend/convex/sitemap.tspackages/i18n/src/brand.tspackages/i18n/src/strings.ts
| expect(dialog.textContent?.toLowerCase()).not.toContain("cont "); | ||
| expect(dialog.textContent?.toLowerCase()).not.toContain("log in"); | ||
| expect(dialog.textContent?.toLowerCase()).not.toContain("sign"); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use word-boundary matching instead of bare substring checks.
not.toContain("sign") would false-positive on words like "design" or "signal", and not.toContain("cont ") misses "cont," or "cont." — both creating fragile test assertions. Use regex with word boundaries for accuracy.
💚 Proposed fix
- expect(dialog.textContent?.toLowerCase()).not.toContain("cont ");
- expect(dialog.textContent?.toLowerCase()).not.toContain("log in");
- expect(dialog.textContent?.toLowerCase()).not.toContain("sign");
+ expect(dialog.textContent?.toLowerCase()).not.toMatch(/\bcont\b/);
+ expect(dialog.textContent?.toLowerCase()).not.toMatch(/\b(log in|sign in|sign up)\b/);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(dialog.textContent?.toLowerCase()).not.toContain("cont "); | |
| expect(dialog.textContent?.toLowerCase()).not.toContain("log in"); | |
| expect(dialog.textContent?.toLowerCase()).not.toContain("sign"); | |
| expect(dialog.textContent?.toLowerCase()).not.toMatch(/\bcont\b/); | |
| expect(dialog.textContent?.toLowerCase()).not.toMatch(/\b(log in|sign in|sign up)\b/); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/MiezOnboarding.test.tsx` around lines 50 - 52, Update
the text assertions in the onboarding dialog test to use case-insensitive
regular expressions with word boundaries instead of bare toContain checks.
Ensure “cont” matches as a standalone word regardless of punctuation or spacing,
and “sign” does not match words such as “design” or “signal”; preserve the
existing prohibition on “log in.”
| @@ -1,5 +1,6 @@ | |||
| import { createFileRoute } from "@tanstack/react-router"; | |||
| import { StaticPage, StaticSection } from "@/components/layout/StaticPage"; | |||
| import { ContactForm } from "@/components/contact-form"; | |||
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Make the embedded contact form announceable to assistive technology.
ContactForm omits aria-label on its inputs/textarea, and its successful-submit message has no role or aria-live; users of screen readers may not receive the submission result.
Proposed fix
- <div className="rounded-lg border border-border bg-muted/30 p-4 text-sm">
+ <div
+ role="status"
+ aria-live="polite"
+ className="rounded-lg border border-border bg-muted/30 p-4 text-sm"
+ >
...
<Input
id="contact-name"
+ aria-label="Nume"
...
<Input
id="contact-email"
+ aria-label="E-mail"
...
<Input
id="contact-subject"
+ aria-label="Subiect"
...
<textarea
id="contact-message"
+ aria-label="Mesaj"As per coding guidelines, “Forms must have aria-label on inputs and ARIA live regions (aria-live, role) on status messages.” As per path instructions, “Focus on web performance and accessibility.”
Also applies to: 23-30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/routes/contact.tsx` at line 3, Update ContactForm so every input
and textarea has an appropriate aria-label, and mark the successful-submit
status message with the required role and aria-live attributes. Preserve the
existing form behavior and message content while ensuring assistive technology
announces both fields and submission results.
Sources: Coding guidelines, Path instructions
| handledByEmail: | ||
| (admin as { email?: string } | null | undefined)?.email ?? undefined, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Replace type assertion with proper typing for handledByEmail.
The cast (admin as { email?: string } | null | undefined)?.email bypasses TypeScript's type system. If requireAdminUser's return type doesn't include email, this silently yields undefined instead of a compile error. The root cause is likely that requireAdminUser returns a generic or incomplete type. Fix the return type of requireAdminUser to include email, then access admin.email directly.
As per path instructions for packages/backend/**: "Check for proper error handling" and "Verify authentication and authorization logic."
♻️ Proposed refactor
- await ctx.db.patch(messageId, {
- status: "handled",
- handledAt: Date.now(),
- handledByEmail:
- (admin as { email?: string } | null | undefined)?.email ?? undefined,
- });
+ await ctx.db.patch(messageId, {
+ status: "handled",
+ handledAt: Date.now(),
+ handledByEmail: admin?.email ?? undefined,
+ });(Requires requireAdminUser to return a type with email: string — verify and update its signature in ./lib/betaAccess.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/backend/convex/contact.ts` around lines 117 - 118, Update
requireAdminUser in lib/betaAccess to return a type that explicitly includes the
administrator’s email, preserving its existing authentication and authorization
behavior. Then update the handledByEmail assignment in the contact flow to
access admin.email directly without a type assertion, while retaining
appropriate null/undefined handling if the helper permits it.
Source: Path instructions
SEO / AI accessibility: - Add /llms.txt (llmstxt.org index) so AI crawlers can discover and cite the site, its sections, and how the AI-generated summaries relate to the original publications. - Consolidate event-page structured data to a single NewsArticle block in <head> (was two conflicting Article + NewsArticle blocks); keep the L1 AI-Act IPTC generation marking + isBasedOn source list; inLanguage=ro. - Add noindex,nofollow to the tokenized reset-password route. Rebrand crawler + email from Biviant to Miez: - Crawler UA BiviantBot -> MiezBot, info URL -> https://www.miez.news/bot (tdmPolicy, botIdentity, articleExtraction, /bot page, /publishers, footer string, and all crawler tests). - Backend BRAND_NAME Biviant -> Miez (was out of sync with i18n). - Email hello@biviant.com -> hello@miez.news; site/unsub/share URLs and link text -> www.miez.news (config, emails, auth). - Waitlist consent text -> Miez, version bumped to 2026-07-12.v1 to keep prior signups tied to their original wording. Apple bundle ids (com.biviant.*) and the native iOS project are left unchanged (separate rebrand; changing them breaks native auth). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary by CodeRabbit