Conversation
…ion baselines - PRD and execution plan for TailwindCSS v3 → v4 migration - Phase 0 audit: Tailwind configs, Ember CSS collisions, specificity strategy - Playwright visual regression suite with golden baselines for 9 admin screens Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated shade deps: tailwindcss@^4, @tailwindcss/vite, @tailwindcss/postcss, tw-animate-css - Removed v3 deps: postcss-import, autoprefixer, @tailwindcss/forms, @tailwindcss/line-clamp - Added @tailwindcss/vite plugin to admin's Vite config - Replaced @tailwind directives with unlayered v4 imports in shade/styles.css - Added @config directive to bridge existing JS config during transition - Added @source directives in admin/src/index.css for monorepo scanning - Deleted PostCSS configs from admin, posts, stats, activitypub (processed by admin's Vite plugin) - Replaced shade PostCSS config with @tailwindcss/postcss (for Storybook) - Visual regressions expected — will be fixed in Phase 2+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrapped CSS color variables in hsl() and updated config from hsl(var()) to var() - Added tw-animate-css import, consolidated duplicate @layer base blocks - Moved :root/.dark CSS vars out of @layer for v4 compatibility - Renamed outline-none → outline-hidden (42 occurrences, 18 files) - Renamed rounded-sm → rounded-xs (18 occurrences, 11 files) - Replaced theme() calls with actual values in preflight.css and sidebar.tsx - Converted ! important prefix to suffix syntax (~30 occurrences, 9 files) - Fixed hsl(var(--X)) → var(--X) in chart components to prevent double-wrapping (21 files) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Converted CSS var shorthand from v3 bracket syntax to v4 parentheses: `w-[--sidebar-width]` → `w-(--sidebar-width)` across sidebar, chart, hover-card, popover, and select components (fixes sidebar layout) - Changed `important: '.shade'` to `important: true` in tailwind config (selector-based important not supported in v4) - Removed duplicate `@import "@tryghost/shade/styles.css"` from activitypub, posts, and stats CSS — these caused triple-duplication of all Tailwind utilities, with later copies overriding responsive variants like `lg:grid` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The v4 `@config` bridge doesn't support selector-based `important: '.shade'`, so Phase 3 used `important: true` as a workaround. This caused every utility to get `!important`, preventing inline styles and custom CSS from overriding Tailwind. Since Tailwind CSS loads after Ember CSS via source order, utilities naturally win over Spirit collision classes without needing `!important`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The font imports appeared after the Tailwind imports which expand to ~13k lines of CSS. This caused @import url() to appear mid-file in the compiled output, violating the CSS spec and triggering PostCSS warnings. Moving them to the top ensures they precede all other statements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughUpdates Tailwind/tooling and standardizes CSS tokens and class syntax across the monorepo. Shade migrates to Tailwind v4 and its PostCSS config now uses Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 5
🧹 Nitpick comments (8)
apps/stats/src/views/Stats/Locations/components/locations-card.tsx (1)
131-131: Use the Tailwind v4 CSS variable shorthand syntax for consistency.Line 131 uses
fill-[var(--chart-blue)], which is valid but longer than necessary. In Tailwind v4,fill-(--chart-blue)is the recommended shorthand specifically for CSS custom properties and produces identical CSS output.Suggested change
- return cn('fill-[var(--chart-blue)]', opacity); + return cn('fill-(--chart-blue)', opacity);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/stats/src/views/Stats/Locations/components/locations-card.tsx` at line 131, Replace the Tailwind class string 'fill-[var(--chart-blue)]' with the Tailwind v4 shorthand 'fill-(--chart-blue)' where it is used (see the cn(...) call in locations-card.tsx), so update the expression that currently returns cn('fill-[var(--chart-blue)]', opacity) to use the shorthand 'fill-(--chart-blue)' while keeping the existing opacity variable intact.apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx (1)
84-87: UseKPI_METRICScolors here to avoid drift.These hardcoded dropdown colors duplicate values already defined in
KPI_METRICS, which can diverge later.♻️ Suggested refactor
- {currentTab === 'visits' && - <KpiTabValue color='var(--chart-blue)' label="Unique visitors" value={kpiValues.visits} /> - } - {currentTab === 'views' && - <KpiTabValue color='var(--chart-teal)' label="Total views" value={kpiValues.views} /> - } + {currentTab === 'visits' && + <KpiTabValue color={KPI_METRICS.visits.color} label="Unique visitors" value={kpiValues.visits} /> + } + {currentTab === 'views' && + <KpiTabValue color={KPI_METRICS.views.color} label="Total views" value={kpiValues.views} /> + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx` around lines 84 - 87, The KpiTabValue calls use hardcoded color strings ('var(--chart-blue)' and 'var(--chart-teal)') which duplicates values already defined in KPI_METRICS; replace those literals with the appropriate color references from KPI_METRICS (e.g., KPI_METRICS.visits.color and KPI_METRICS.views.color or the correct keys) so KpiTabValue receives KPI_METRICS[...] .color for the 'Unique visitors' and 'Total views' branches that check currentTab and use kpiValues, ensuring colors stay in sync with the canonical KPI_METRICS definitions.e2e/visual-regression/capture-baselines.spec.ts (3)
107-108: The 500ms settle wait after style injection may cause flakiness.This hard-coded wait could be replaced by waiting for a visual state or simply removed if the style injection is synchronous. Consider if this is truly necessary.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/visual-regression/capture-baselines.spec.ts` around lines 107 - 108, The hard-coded 500ms settle in the test (page.waitForTimeout(500)) can cause flakiness; replace it by waiting for a concrete visual/state change after the style injection instead of a fixed timeout (for example use page.waitForSelector, page.waitForFunction that checks the injected style is applied, or remove the wait if injection is synchronous) so update the test around the style injection to await a deterministic condition rather than calling page.waitForTimeout(500).
89-114: Test naming doesn't follow the project convention.The coding guidelines specify test names should follow
'Ghost Admin - Feature'for suites and'what is tested - expected outcome'for individual tests.Current:
visual baseline: ${screen.name}
Suggested:Ghost Admin - Visual Regressionfor the suite context (viatest.describe)♻️ Suggested structure
+test.describe('Ghost Admin - Visual Regression', () => { for (const screen of SCREENS) { - test(`visual baseline: ${screen.name}`, async ({page}) => { + test(`${screen.name} - matches baseline`, async ({page}) => { // ... }); } +});As per coding guidelines: "Test suites should follow naming convention: 'Ghost Admin - Feature'".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/visual-regression/capture-baselines.spec.ts` around lines 89 - 114, Replace the free-form test naming inside the SCREENS loop with the project's conventions: wrap the loop in a test.describe block named "Ghost Admin - Visual Regression" and update each individual test(...) title (currently `visual baseline: ${screen.name}`) to the "what is tested - expected outcome" pattern such as `${screen.name} - baseline matches expected` so suites use "Ghost Admin - Feature" and tests follow "what is tested - expected outcome"; adjust only the test.describe and test(...) title strings around the existing loop and keep all existing waits, style injection, and expect(page).toHaveScreenshot(...) logic unchanged.
99-102: Consider replacingwaitForTimeoutwith more deterministic waits.The coding guidelines explicitly prohibit hard-coded waits (
waitForTimeout). While visual regression tests may need extra settling time for complex screens, consider alternatives:
- For chart screens: wait for a specific chart element or data attribute
- Add
waitForselectors to the Screen definitions instead ofextraWaitIf
extraWaitis truly necessary for these screens, document why deterministic waits aren't feasible.♻️ Suggested approach
const SCREENS: Screen[] = [ // ... - {name: 'editor-new-post', path: '/ghost/#/editor/post', extraWait: 2000}, + {name: 'editor-new-post', path: '/ghost/#/editor/post', waitFor: '[data-testid="editor-canvas"]'}, // ... - {name: 'stats', path: '/ghost/#/stats', extraWait: 1000}, + {name: 'stats', path: '/ghost/#/stats', waitFor: '[data-testid="stats-chart"]'}, // ... - {name: 'activitypub', path: '/ghost/#/activitypub', extraWait: 1000}, + {name: 'activitypub', path: '/ghost/#/activitypub', waitFor: '[data-testid="activity-feed"]'}, ];As per coding guidelines: "Never use hard-coded waits (
waitForTimeout) in E2E tests".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@e2e/visual-regression/capture-baselines.spec.ts` around lines 99 - 102, Replace the hard-coded page.waitForTimeout usage tied to screen.extraWait with deterministic waits: add a new optional property on the Screen definitions such as waitForSelector or waitForFunction (or reuse an existing selector field) and, in the capture flow where you currently check screen.extraWait, call page.waitForSelector(screen.waitForSelector) or page.waitForFunction(screen.waitForFunction) to await a concrete DOM/data condition (or wait for a specific chart element/data-attribute) instead of sleeping; if you determine extraWait is absolutely unavoidable, replace the sleep with a short explanatory comment and a documented justification on the Screen entry and retain a minimal fallback timeout, but prefer selector/function-based waits in functions handling the capture logic that reference screen.extraWait/page.waitForTimeout.twcss4-migration-prd.md (1)
200-210: Add language identifier to workflow diagram.Similar to the migration plan, this ASCII diagram should have a language identifier.
📝 Suggested fix
-``` +```text ┌─────────────────────────────────────────────────────────┐ │ AI Agent Workflow │🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@twcss4-migration-prd.md` around lines 200 - 210, The ASCII diagram labeled "AI Agent Workflow" lacks a language identifier; update the fenced code block that begins with the box header "┌─────────────────────────────────────────────────────────┐" (and the title line "│ AI Agent Workflow │") to include a language tag (e.g., ```text) on the opening fence so the diagram matches the migration plan formatting and is rendered consistently.twcss4-phase0-audit.md (1)
129-139: Add language identifier to fenced code block.The code block showing the CSS entry point chain should have a language identifier for better rendering and syntax highlighting. Since it's a text diagram, use
textorplaintext.📝 Suggested fix
-``` +```text apps/admin/src/index.css └─ `@import` "@tryghost/shade/styles.css" (= apps/shade/styles.css)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@twcss4-phase0-audit.md` around lines 129 - 139, The fenced code block that shows the CSS entry point chain (starting with "apps/admin/src/index.css" and the indented imports like "@tryghost/shade/styles.css", "./preflight.css", "tailwindcss/base", etc.) needs a language identifier for proper rendering; update the opening fence to include a language (e.g., ```text or ```plaintext) and keep the same block content so the diagram renders with syntax highlighting/monospace formatting.twcss4-migration-plan.md (1)
200-210: Add language identifier to workflow diagram.The ASCII workflow diagram should have a language identifier for consistent rendering.
📝 Suggested fix
-``` +```text ┌─────────────────────────────────────────────────────────┐ │ AI Agent Workflow │🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@twcss4-migration-plan.md` around lines 200 - 210, The ASCII workflow diagram code fence lacks a language identifier; update the diagram's fenced code block so the opening fence includes a language specifier (e.g., change ``` to ```text) to ensure consistent rendering of the ASCII workflow diagram in the document; locate the ASCII block shown beginning with "┌─────────────────────────────────────────────────────────┐" and modify its opening triple-backtick to include the language identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/admin/src/index.css`:
- Around line 1-6: Update the Stylelint configuration to allow Tailwind v4
directives by modifying .stylelintrc.json: adjust the scss/at-rule-no-unknown
rule to include ignoreAtRules with "source", "import", "layer", "apply", and
"tailwind", and set no-invalid-position-at-import-rule to null so the `@source`
and `@import` directives in apps/admin/src/index.css no longer trigger lint
errors; ensure these changes are added alongside existing rules (keep existing
extends and other rules intact).
In `@apps/shade/preflight.css`:
- Line 37: The font-family declaration uses unquoted multi-word family names
causing stylelint errors; update the declaration(s) (the font-family line
containing "Inter, -apple-system, BlinkMacSystemFont, avenir next, avenir,
helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif")
to wrap multi-word font family names in quotes (e.g., "Avenir Next", "Helvetica
Neue", "Segoe UI") while leaving single-word generic families and system
identifiers unquoted, and apply the same change to the other occurrence
referenced in the comment.
In `@apps/shade/src/components/features/post-share-modal/post-share-modal.tsx`:
- Line 116: The LinkedIn share anchor in PostShareModal uses an insecure URL
scheme; update the href in the anchor (the <a> element that builds
`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`)
to use `https://www.linkedin.com/...` instead of `http://` so the constructed
share URL uses HTTPS while keeping the existing encodedPostTitle and
encodedPostURL variables unchanged.
- Around line 107-117: The icon-only anchor elements (the <a> tags with
className 'flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted
px-3 ...' that link to Twitter/X, Threads, Facebook, and LinkedIn) need explicit
accessible names: add an aria-label to each anchor describing the action (e.g.,
aria-label="Share on X" or "Share on Twitter" for the anchor whose svg uses
className "social-x_svg__x", aria-label="Share on Threads" for the svg with
clipPath id "social-threads_svg__clip0_351_18008", aria-label="Share on
Facebook" for the svg with title "social-facebook", and aria-label="Share on
LinkedIn" for the svg with className "social-linkedin_svg__linkedin"); ensure
the aria-label text is concise and matches the platform name.
In `@apps/shade/src/components/ui/chart.tsx`:
- Line 372: Replace the nonexistent CSS variable used for SVG fill in the Chart
component: change the fill attribute currently set to "var(--gray-500)" (used
for axis labels/secondary text in apps/shade/src/components/ui/chart.tsx) to the
defined variable "var(--muted-foreground)"; update the JSX SVG element(s) or any
occurrences of "var(--gray-500)" in chart rendering (e.g., axis label/legend
fills) to "var(--muted-foreground)" so the color resolves in both light and dark
modes.
---
Nitpick comments:
In `@apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx`:
- Around line 84-87: The KpiTabValue calls use hardcoded color strings
('var(--chart-blue)' and 'var(--chart-teal)') which duplicates values already
defined in KPI_METRICS; replace those literals with the appropriate color
references from KPI_METRICS (e.g., KPI_METRICS.visits.color and
KPI_METRICS.views.color or the correct keys) so KpiTabValue receives
KPI_METRICS[...] .color for the 'Unique visitors' and 'Total views' branches
that check currentTab and use kpiValues, ensuring colors stay in sync with the
canonical KPI_METRICS definitions.
In `@apps/stats/src/views/Stats/Locations/components/locations-card.tsx`:
- Line 131: Replace the Tailwind class string 'fill-[var(--chart-blue)]' with
the Tailwind v4 shorthand 'fill-(--chart-blue)' where it is used (see the
cn(...) call in locations-card.tsx), so update the expression that currently
returns cn('fill-[var(--chart-blue)]', opacity) to use the shorthand
'fill-(--chart-blue)' while keeping the existing opacity variable intact.
In `@e2e/visual-regression/capture-baselines.spec.ts`:
- Around line 107-108: The hard-coded 500ms settle in the test
(page.waitForTimeout(500)) can cause flakiness; replace it by waiting for a
concrete visual/state change after the style injection instead of a fixed
timeout (for example use page.waitForSelector, page.waitForFunction that checks
the injected style is applied, or remove the wait if injection is synchronous)
so update the test around the style injection to await a deterministic condition
rather than calling page.waitForTimeout(500).
- Around line 89-114: Replace the free-form test naming inside the SCREENS loop
with the project's conventions: wrap the loop in a test.describe block named
"Ghost Admin - Visual Regression" and update each individual test(...) title
(currently `visual baseline: ${screen.name}`) to the "what is tested - expected
outcome" pattern such as `${screen.name} - baseline matches expected` so suites
use "Ghost Admin - Feature" and tests follow "what is tested - expected
outcome"; adjust only the test.describe and test(...) title strings around the
existing loop and keep all existing waits, style injection, and
expect(page).toHaveScreenshot(...) logic unchanged.
- Around line 99-102: Replace the hard-coded page.waitForTimeout usage tied to
screen.extraWait with deterministic waits: add a new optional property on the
Screen definitions such as waitForSelector or waitForFunction (or reuse an
existing selector field) and, in the capture flow where you currently check
screen.extraWait, call page.waitForSelector(screen.waitForSelector) or
page.waitForFunction(screen.waitForFunction) to await a concrete DOM/data
condition (or wait for a specific chart element/data-attribute) instead of
sleeping; if you determine extraWait is absolutely unavoidable, replace the
sleep with a short explanatory comment and a documented justification on the
Screen entry and retain a minimal fallback timeout, but prefer
selector/function-based waits in functions handling the capture logic that
reference screen.extraWait/page.waitForTimeout.
In `@twcss4-migration-plan.md`:
- Around line 200-210: The ASCII workflow diagram code fence lacks a language
identifier; update the diagram's fenced code block so the opening fence includes
a language specifier (e.g., change ``` to ```text) to ensure consistent
rendering of the ASCII workflow diagram in the document; locate the ASCII block
shown beginning with
"┌─────────────────────────────────────────────────────────┐" and modify its
opening triple-backtick to include the language identifier.
In `@twcss4-migration-prd.md`:
- Around line 200-210: The ASCII diagram labeled "AI Agent Workflow" lacks a
language identifier; update the fenced code block that begins with the box
header "┌─────────────────────────────────────────────────────────┐" (and the
title line "│ AI Agent Workflow │") to
include a language tag (e.g., ```text) on the opening fence so the diagram
matches the migration plan formatting and is rendered consistently.
In `@twcss4-phase0-audit.md`:
- Around line 129-139: The fenced code block that shows the CSS entry point
chain (starting with "apps/admin/src/index.css" and the indented imports like
"@tryghost/shade/styles.css", "./preflight.css", "tailwindcss/base", etc.) needs
a language identifier for proper rendering; update the opening fence to include
a language (e.g., ```text or ```plaintext) and keep the same block content so
the diagram renders with syntax highlighting/monospace formatting.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (10)
e2e/visual-regression/baselines/activitypub.pngis excluded by!**/*.pnge2e/visual-regression/baselines/dashboard.pngis excluded by!**/*.pnge2e/visual-regression/baselines/editor-new-post.pngis excluded by!**/*.pnge2e/visual-regression/baselines/members-list.pngis excluded by!**/*.pnge2e/visual-regression/baselines/pages-list.pngis excluded by!**/*.pnge2e/visual-regression/baselines/posts-list.pngis excluded by!**/*.pnge2e/visual-regression/baselines/settings.pngis excluded by!**/*.pnge2e/visual-regression/baselines/stats.pngis excluded by!**/*.pnge2e/visual-regression/baselines/tags-list.pngis excluded by!**/*.pngyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (67)
apps/activitypub/package.jsonapps/activitypub/postcss.config.cjsapps/activitypub/src/styles/index.cssapps/admin/postcss.config.jsapps/admin/src/index.cssapps/admin/vite.config.tsapps/posts/postcss.config.cjsapps/posts/src/styles/index.cssapps/posts/src/views/PostAnalytics/Newsletter/components/newsletter-radial-chart.tsxapps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsxapps/posts/src/views/PostAnalytics/Overview/components/newsletter-overview.tsxapps/posts/src/views/PostAnalytics/Overview/components/web-overview.tsxapps/posts/src/views/PostAnalytics/Web/components/kpis.tsxapps/shade/package.jsonapps/shade/postcss.config.cjsapps/shade/preflight.cssapps/shade/src/components/features/color-picker/color-picker.tsxapps/shade/src/components/features/post-share-modal/post-share-modal.tsxapps/shade/src/components/ui/badge.tsxapps/shade/src/components/ui/button.tsxapps/shade/src/components/ui/card.stories.tsxapps/shade/src/components/ui/card.tsxapps/shade/src/components/ui/chart.stories.tsxapps/shade/src/components/ui/chart.tsxapps/shade/src/components/ui/checkbox.tsxapps/shade/src/components/ui/command.tsxapps/shade/src/components/ui/dialog.tsxapps/shade/src/components/ui/dropdown-menu.tsxapps/shade/src/components/ui/filters.tsxapps/shade/src/components/ui/gh-chart.stories.tsxapps/shade/src/components/ui/gh-chart.tsxapps/shade/src/components/ui/hover-card.tsxapps/shade/src/components/ui/input-group.stories.tsxapps/shade/src/components/ui/input-group.tsxapps/shade/src/components/ui/input.tsxapps/shade/src/components/ui/kbd.tsxapps/shade/src/components/ui/popover.tsxapps/shade/src/components/ui/select.tsxapps/shade/src/components/ui/sheet.tsxapps/shade/src/components/ui/sidebar.tsxapps/shade/src/components/ui/sonner.tsxapps/shade/src/components/ui/switch.tsxapps/shade/src/components/ui/tabs.tsxapps/shade/src/components/ui/textarea.tsxapps/shade/src/components/ui/toggle.tsxapps/shade/src/providers/shade-provider.tsxapps/shade/styles.cssapps/shade/tailwind.config.cjsapps/stats/postcss.config.cjsapps/stats/src/components/chart/custom-tooltip-content.tsxapps/stats/src/styles/index.cssapps/stats/src/views/Stats/Growth/components/growth-kpis.tsxapps/stats/src/views/Stats/Growth/components/new-subscribers-cadence.tsxapps/stats/src/views/Stats/Growth/components/paid-subscription-change-chart.tsxapps/stats/src/views/Stats/Locations/components/locations-card.tsxapps/stats/src/views/Stats/Newsletters/components/newsletters-kpis.tsxapps/stats/src/views/Stats/Overview/components/overview-kpis.tsxapps/stats/src/views/Stats/Web/components/web-kpis.tsxapps/stats/src/views/Stats/Web/web.tsxe2e/visual-regression/.auth/.gitkeepe2e/visual-regression/.gitignoree2e/visual-regression/auth.setup.tse2e/visual-regression/capture-baselines.spec.tse2e/visual-regression/playwright.config.tstwcss4-migration-plan.mdtwcss4-migration-prd.mdtwcss4-phase0-audit.md
💤 Files with no reviewable changes (6)
- apps/stats/src/styles/index.css
- apps/stats/postcss.config.cjs
- apps/activitypub/src/styles/index.css
- apps/posts/postcss.config.cjs
- apps/activitypub/postcss.config.cjs
- apps/admin/postcss.config.js
| @source "../../shade/src/**/*.{ts,tsx}"; | ||
| @source "../../posts/src/**/*.{ts,tsx}"; | ||
| @source "../../stats/src/**/*.{ts,tsx}"; | ||
| @source "../../activitypub/src/**/*.{ts,tsx}"; | ||
| @source "../../admin-x-settings/src/**/*.{ts,tsx}"; | ||
| @source "../../admin-x-design-system/src/**/*.{ts,tsx}"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether repo lint configs are prepared for Tailwind v4 directives like `@source`
set -euo pipefail
echo "== Candidate lint config files =="
fd -HI 'biome.json|biome.jsonc|.stylelintrc|.stylelintrc.json|.stylelintrc.js|.stylelintrc.cjs|stylelint.config.js|stylelint.config.cjs'
echo
echo "== Relevant config lines (tailwind, at-rule-no-unknown, `@source`) =="
fd -HI 'biome.json|biome.jsonc|.stylelintrc|.stylelintrc.json|.stylelintrc.js|.stylelintrc.cjs|stylelint.config.js|stylelint.config.cjs' \
| xargs -r rg -n -C2 'tailwind|at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|@source'
echo
echo "== Existing `@source` usage =="
rg -n '^\s*@source\b' -g '*.css'Repository: TryGhost/Ghost
Length of output: 191
🏁 Script executed:
#!/bin/bash
echo "== .stylelintrc.json content =="
cat .stylelintrc.json
echo
echo "== biome.json content =="
cat biome.json
echo
echo "== Searching for `@source` usage in CSS =="
find . -name "*.css" -type f | head -20 | xargs grep -l "@source" 2>/dev/null || echo "No `@source` found or error in search"
echo
echo "== Check Tailwind/lint versions in package.json (root) =="
grep -A2 -B2 'tailwindcss\|stylelint\|biome' package.json 2>/dev/null || echo "Not found in root package.json"Repository: TryGhost/Ghost
Length of output: 3054
🏁 Script executed:
#!/bin/bash
echo "== apps/admin/package.json (check Tailwind version) =="
cat apps/admin/package.json 2>/dev/null | grep -A2 -B2 'tailwindcss\|stylelint\|version' | head -30 || echo "Not found or error"
echo
echo "== Tailwind config files =="
find . -maxdepth 3 -name "tailwind.config.*" -o -name "tailwind.json" 2>/dev/null | head -5
echo
echo "== Check if at-rule-no-unknown is in stylelintrc.json =="
grep -i "at-rule" .stylelintrc.json
echo
echo "== Check apps/admin/src/index.css content =="
head -20 apps/admin/src/index.css
echo
echo "== Look for Tailwind v4+ indicators (postcss config) =="
find . -maxdepth 2 -name "postcss.config.*" 2>/dev/null | xargs cat 2>/dev/null || echo "No postcss config found"Repository: TryGhost/Ghost
Length of output: 1299
🏁 Script executed:
#!/bin/bash
echo "== apps/admin/tailwind.config.js =="
cat apps/admin/tailwind.config.js
echo
echo "== Search for ignoreAtRules in any stylelint config =="
rg -n "ignoreAtRules|ignorePseudoClasses|ignoreCustomProperties" .stylelintrc* 2>/dev/null || echo "Not found"
echo
echo "== Check if there's a .stylelintignore =="
ls -la .stylelintignore 2>/dev/null || echo "Not found"
echo
echo "== Look for Tailwind `@layer/`@import/@source patterns in config =="
rg -n "@source|@layer|@import" .stylelintrc.json 2>/dev/null || echo "Not found in stylelint config"
echo
echo "== Check npm packages for stylelint version (if package-lock exists) =="
grep -A3 '"stylelint"' package-lock.json 2>/dev/null | head -10 || echo "Check failed"Repository: TryGhost/Ghost
Length of output: 2842
🏁 Script executed:
#!/bin/bash
echo "== Check Tailwind version in package-lock.json =="
grep -A5 '"tailwindcss":' package-lock.json 2>/dev/null | head -20 || echo "Not found"
echo
echo "== Check for CI config files =="
ls -la .github/workflows/*.yml 2>/dev/null | head -5 || echo "No github workflows"
echo
echo "== Search for any linting errors in recent commits =="
git log --oneline --all -20 2>/dev/null | grep -i 'lint\|style\|css' || echo "No lint-related commits found"
echo
echo "== Check if there are test/lint scripts in package.json =="
grep -A1 '"lint"\|"style"\|"test"' package.json | head -20
echo
echo "== Look for error reports mentioning `@source` or at-rule-no-unknown =="
rg -i "at-rule-no-unknown|@source.*error" . --type=text 2>/dev/null | head -10 || echo "No error reports found"Repository: TryGhost/Ghost
Length of output: 1038
🏁 Script executed:
#!/bin/bash
echo "== Check CI config for lint tasks =="
grep -A10 "lint\|stylelint" .github/workflows/ci.yml | head -30
echo
echo "== Check the recent Tailwind v4 migration commit =="
git show ec5bea06 --stat 2>/dev/null | head -40
echo
echo "== Check if there are any known Tailwind v4 config docs/comments =="
rg -i "tailwind.*v4|v4.*tailwind" . --type=text 2>/dev/null | head -10
echo
echo "== Check package.json for Tailwind version =="
cat package.json | grep -A30 '"dependencies"\|"devDependencies"' | grep -i tailwind || echo "Not in root package.json"
echo
echo "== Check apps/admin for Tailwind version =="
cat apps/admin/package.json | grep -A30 '"dependencies"\|"devDependencies"' | grep -i tailwind || echo "Not in apps/admin/package.json"Repository: TryGhost/Ghost
Length of output: 3454
🏁 Script executed:
#!/bin/bash
echo "== Check if apps/admin has its own stylelint config =="
find apps/admin -name ".stylelintrc*" -o -name "stylelint.config.*" 2>/dev/null
echo
echo "== Search for any stylelint overrides or Tailwind exceptions =="
rg -i "stylelint|@source|at-rule" apps/admin 2>/dev/null | head -10
echo
echo "== Check the parent packages.json for stylelint-config-standard-scss =="
rg '"stylelint-config-standard-scss"' . 2>/dev/null
echo
echo "== Look for any comments or documentation about lint config in the project =="
find . -maxdepth 3 -name "*.md" -exec grep -l "stylelint\|@source" {} \; 2>/dev/null
echo
echo "== Check if the index.css file was recently added or modified =="
git log --oneline -10 -- apps/admin/src/index.css 2>/dev/null || echo "File history check failed"
echo
echo "== Look at the actual diff of the reviewed file =="
git show HEAD:apps/admin/src/index.css 2>/dev/null | head -10Repository: TryGhost/Ghost
Length of output: 1419
🏁 Script executed:
#!/bin/bash
echo "== Check Tailwind v4 migration plan for lint config updates =="
cat ./twcss4-migration-plan.md 2>/dev/null | head -100
echo
echo "== Check Tailwind v4 migration PRD for lint config =="
grep -A10 -B10 "stylelint\|lint\|@source" ./twcss4-migration-prd.md 2>/dev/null | head -50
echo
echo "== Search for any Tailwind-related updates to stylelint or biome config =="
git log --oneline --all -- .stylelintrc.json biome.json 2>/dev/null | head -10
echo
echo "== Check if stylelint-config-standard-scss was updated recently =="
git log --oneline --all -- package-lock.json 2>/dev/null | grep -i stylelint || git log --oneline -5 -- package-lock.json 2>/dev/null | head -5Repository: TryGhost/Ghost
Length of output: 8889
🏁 Script executed:
#!/bin/bash
echo "== Check the exact commit that added `@source` directives =="
git show ec5bea06 -- apps/admin/src/index.css 2>/dev/null | head -40
echo
echo "== Check if Tailwind docs mention stylelint support for `@source` =="
rg -i "stylelint.*@source|@source.*stylelint" . 2>/dev/null | head -5 || echo "Not found in repo"
echo
echo "== Try running stylelint on the file to see if it actually reports errors =="
if command -v stylelint &> /dev/null; then
cd apps/admin && stylelint src/index.css 2>&1 || echo "stylelint execution attempted"
cd - > /dev/null
else
echo "stylelint not available in PATH"
fi
echo
echo "== Check if package has stylelint installed =="
rg '"stylelint"' package.json | head -5
echo
echo "== Look for any linting errors or warnings in recent logs =="
git log --oneline --grep="lint\|stylelint\|@source" --all 2>/dev/null | head -10Repository: TryGhost/Ghost
Length of output: 3250
🏁 Script executed:
#!/bin/bash
echo "== Check the recommended fix for stylelint + Tailwind v4 =="
rg -i "ignoreAtRules|scss/at-rule-no-unknown" . --type=json | head -10 || echo "Not configured"
echo
echo "== Check if there's a Tailwind-specific stylelint config =="
rg "stylelint-config-tailwindcss\|tailwindcss.*stylelint" . 2>/dev/null | head -5 || echo "Not found"
echo
echo "== Look for how other Ghost/monorepo projects handle this =="
find . -path "*/node_modules" -prune -o -name ".stylelintrc*" -type f -print | xargs grep -l "at-rule\|tailwind" 2>/dev/null || echo "No other configs found"
echo
echo "== Check if `@import` position error is also from `@tailwindcss/vite` incompatibility =="
rg "no-invalid-position-at-import-rule" . --type=json 2>/dev/null || echo "Rule not found in config"
echo
echo "== Verify if this file actually gets linted in CI =="
grep -r "apps/admin/src" .github/workflows/ci.yml 2>/dev/null || echo "No explicit path filter"Repository: TryGhost/Ghost
Length of output: 449
🏁 Script executed:
#!/bin/bash
echo "== Check if there's a more recent commit after ec5bea06 that fixes lint config =="
git log --oneline ec5bea06..HEAD 2>/dev/null | head -10
echo
echo "== Check the exact CI status for affected lint tasks =="
git log --oneline --grep="lint\|config" -20 2>/dev/null | head -15
echo
echo "== Look at current .stylelintrc.json to see what needs to be added =="
cat .stylelintrc.json
echo
echo "== Research the proper stylelint configuration for Tailwind v4 =="
echo "Checking for known patterns in similar projects or comments..."
rg -i "tailwind.*v4|@source" . --type=md | head -10 || echo "No markdown docs about this"
echo
echo "== Check if apps/admin is actually being linted =="
rg "apps/admin" .github/workflows/ci.yml | head -5Repository: TryGhost/Ghost
Length of output: 3499
Configure stylelint to allow Tailwind v4 directives.
The @source directives on lines 1–6 are triggering scss/at-rule-no-unknown errors, and the @import on line 8 is triggering no-invalid-position-at-import-rule. Update .stylelintrc.json to allowlist these Tailwind v4 at-rules:
{
"extends": ["stylelint-config-standard-scss"],
"rules": {
"scss/at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["source", "import", "layer", "apply", "tailwind"]
}
],
"no-invalid-position-at-import-rule": null,
// ... rest of existing rules
}
}Without this, the lint task will fail on this file.
🧰 Tools
🪛 Biome (2.4.4)
[error] 1-1: Tailwind-specific syntax is disabled.
(parse)
[error] 2-2: Tailwind-specific syntax is disabled.
(parse)
[error] 3-3: Tailwind-specific syntax is disabled.
(parse)
[error] 4-4: Tailwind-specific syntax is disabled.
(parse)
[error] 5-5: Tailwind-specific syntax is disabled.
(parse)
[error] 6-6: Tailwind-specific syntax is disabled.
(parse)
🪛 Stylelint (17.3.0)
[error] 1-1: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 2-2: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 3-3: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 4-4: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 5-5: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
[error] 6-6: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/admin/src/index.css` around lines 1 - 6, Update the Stylelint
configuration to allow Tailwind v4 directives by modifying .stylelintrc.json:
adjust the scss/at-rule-no-unknown rule to include ignoreAtRules with "source",
"import", "layer", "apply", and "tailwind", and set
no-invalid-position-at-import-rule to null so the `@source` and `@import` directives
in apps/admin/src/index.css no longer trigger lint errors; ensure these changes
are added alongside existing rules (keep existing extends and other rules
intact).
| "Segoe UI Symbol", | ||
| "Noto Color Emoji" | ||
| ); /* 4 */ | ||
| font-family: Inter, -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif; /* 4 */ |
There was a problem hiding this comment.
Fix stylelint quote errors in font-family values.
Line 37 and Line 111 include multi-word font family names without required quotes, which triggers font-family-name-quotes.
🛠️ Suggested fix
- font-family: Inter, -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif; /* 4 */
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "avenir next", avenir, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", arial, sans-serif; /* 4 */
- font-family: Consolas, Liberation Mono, Menlo, Courier, monospace; /* 1 */
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; /* 1 */Also applies to: 111-111
🧰 Tools
🪛 Stylelint (17.3.0)
[error] 37-37: Expected quotes around "avenir next" (font-family-name-quotes)
(font-family-name-quotes)
[error] 37-37: Expected quotes around "helvetica neue" (font-family-name-quotes)
(font-family-name-quotes)
[error] 37-37: Expected quotes around "segoe ui" (font-family-name-quotes)
(font-family-name-quotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/preflight.css` at line 37, The font-family declaration uses
unquoted multi-word family names causing stylelint errors; update the
declaration(s) (the font-family line containing "Inter, -apple-system,
BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu,
roboto, noto, segoe ui, arial, sans-serif") to wrap multi-word font family names
in quotes (e.g., "Avenir Next", "Helvetica Neue", "Segoe UI") while leaving
single-word generic families and system identifiers unquoted, and apply the same
change to the other occurrence referenced in the comment.
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://twitter.com/intent/tweet?text=${encodedPostTitle}%0A${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <svg aria-hidden="true" viewBox="0 0 24 24"><path className="social-x_svg__x" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path></svg> | ||
| </a> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-sm bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://threads.net/intent/post?text=${encodedPostURLTitle}`} rel="noopener noreferrer" target='_blank'> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://threads.net/intent/post?text=${encodedPostURLTitle}`} rel="noopener noreferrer" target='_blank'> | ||
| <svg fill="none" viewBox="0 0 18 18"><g clipPath="url(#social-threads_svg__clip0_351_18008)"><path d="M13.033 8.38a5.924 5.924 0 00-.223-.102c-.13-2.418-1.452-3.802-3.67-3.816h-.03c-1.327 0-2.43.566-3.11 1.597l1.22.837c.507-.77 1.304-.934 1.89-.934h.02c.73.004 1.282.217 1.639.63.26.302.433.72.519 1.245a9.334 9.334 0 00-2.097-.101c-2.109.121-3.465 1.351-3.374 3.06.047.868.478 1.614 1.216 2.1.624.413 1.428.614 2.263.568 1.103-.06 1.969-.48 2.572-1.25.459-.585.749-1.342.877-2.296.526.317.915.735 1.13 1.236.366.854.387 2.255-.756 3.398-1.003 1.002-2.207 1.435-4.028 1.448-2.02-.015-3.547-.663-4.54-1.925-.93-1.182-1.41-2.89-1.428-5.075.018-2.185.498-3.893 1.428-5.075.993-1.262 2.52-1.91 4.54-1.925 2.034.015 3.588.666 4.62 1.934.505.622.886 1.405 1.137 2.317l1.43-.382c-.305-1.122-.784-2.09-1.436-2.892C13.52 1.35 11.587.517 9.096.5h-.01C6.6.517 4.689 1.354 3.404 2.986 2.262 4.44 1.672 6.46 1.652 8.994v.012c.02 2.534.61 4.555 1.752 6.008C4.69 16.646 6.6 17.483 9.086 17.5h.01c2.21-.015 3.768-.594 5.051-1.876 1.68-1.678 1.629-3.78 1.075-5.07-.397-.927-1.154-1.678-2.189-2.175zm-3.816 3.587c-.924.052-1.884-.363-1.932-1.252-.035-.659.47-1.394 1.99-1.482a8.9 8.9 0 01.512-.014c.552 0 1.068.053 1.538.156-.175 2.187-1.203 2.542-2.108 2.592z" fill="#000"></path></g><defs><clipPath id="social-threads_svg__clip0_351_18008"><path d="M0 0h17v17H0z" fill="#fff" transform="translate(.5 .5)"></path></clipPath></defs></svg> | ||
| </a> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-sm bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://www.facebook.com/sharer/sharer.php?u=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://www.facebook.com/sharer/sharer.php?u=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <svg fill="none" viewBox="0 0 40 40"><title>social-facebook</title><path className="social-facebook_svg__fb" d="M20 40.004c11.046 0 20-8.955 20-20 0-11.046-8.954-20-20-20s-20 8.954-20 20c0 11.045 8.954 20 20 20z" fill="#1977f3"></path><path d="M27.785 25.785l.886-5.782h-5.546V16.25c0-1.58.773-3.125 3.26-3.125h2.522V8.204s-2.29-.39-4.477-.39c-4.568 0-7.555 2.767-7.555 7.781v4.408h-5.08v5.782h5.08v13.976a20.08 20.08 0 003.125.242c1.063 0 2.107-.085 3.125-.242V25.785h4.66z" fill="#fff"></path></svg> | ||
| </a> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-sm bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <svg fill="none" viewBox="0 0 16 16"><g clipPath="url(#social-linkedin_svg__clip0_537_833)"><path className="social-linkedin_svg__linkedin" clipRule="evenodd" d="M1.778 16h12.444c.982 0 1.778-.796 1.778-1.778V1.778C16 .796 15.204 0 14.222 0H1.778C.796 0 0 .796 0 1.778v12.444C0 15.204.796 16 1.778 16z" fill="#007ebb" fillRule="evenodd"></path><path clipRule="evenodd" d="M13.778 13.778h-2.374V9.734c0-1.109-.421-1.729-1.299-1.729-.955 0-1.453.645-1.453 1.729v4.044H6.363V6.074h2.289v1.038s.688-1.273 2.322-1.273c1.634 0 2.804.997 2.804 3.061v4.878zM3.634 5.065c-.78 0-1.411-.636-1.411-1.421s.631-1.422 1.41-1.422c.78 0 1.411.637 1.411 1.422 0 .785-.631 1.421-1.41 1.421zm-1.182 8.713h2.386V6.074H2.452v7.704z" fill="#fff" fillRule="evenodd"></path></g><defs><clipPath id="social-linkedin_svg__clip0_537_833"><path d="M0 0h16v16H0z" fill="#fff"></path></clipPath></defs></svg> |
There was a problem hiding this comment.
Add accessible names to icon-only share links.
These links are icon-only and need explicit aria-label values so assistive tech can identify each action.
♿ Suggested fix
-<a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://twitter.com/intent/tweet?text=${encodedPostTitle}%0A${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
- <svg aria-hidden="true" viewBox="0 0 24 24"><path className="social-x_svg__x" d="..."></path></svg>
+<a aria-label='Share on X' className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://twitter.com/intent/tweet?text=${encodedPostTitle}%0A${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
+ <svg aria-hidden="true" viewBox="0 0 24 24"><path className="social-x_svg__x" d="..."></path></svg>
</a>
-<a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://threads.net/intent/post?text=${encodedPostURLTitle}`} rel="noopener noreferrer" target='_blank'>
- <svg fill="none" viewBox="0 0 18 18"><g ...></g></svg>
+<a aria-label='Share on Threads' className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://threads.net/intent/post?text=${encodedPostURLTitle}`} rel="noopener noreferrer" target='_blank'>
+ <svg aria-hidden="true" fill="none" viewBox="0 0 18 18"><g ...></g></svg>
</a>
-<a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://www.facebook.com/sharer/sharer.php?u=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
- <svg fill="none" viewBox="0 0 40 40"><title>social-facebook</title><path ...></path></svg>
+<a aria-label='Share on Facebook' className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://www.facebook.com/sharer/sharer.php?u=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
+ <svg aria-hidden="true" fill="none" viewBox="0 0 40 40"><path ...></path></svg>
</a>
-<a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
- <svg fill="none" viewBox="0 0 16 16"><g ...></g></svg>
+<a aria-label='Share on LinkedIn' className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'>
+ <svg aria-hidden="true" fill="none" viewBox="0 0 16 16"><g ...></g></svg>
</a>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/src/components/features/post-share-modal/post-share-modal.tsx`
around lines 107 - 117, The icon-only anchor elements (the <a> tags with
className 'flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted
px-3 ...' that link to Twitter/X, Threads, Facebook, and LinkedIn) need explicit
accessible names: add an aria-label to each anchor describing the action (e.g.,
aria-label="Share on X" or "Share on Twitter" for the anchor whose svg uses
className "social-x_svg__x", aria-label="Share on Threads" for the svg with
clipPath id "social-threads_svg__clip0_351_18008", aria-label="Share on
Facebook" for the svg with title "social-facebook", and aria-label="Share on
LinkedIn" for the svg with className "social-linkedin_svg__linkedin"); ensure
the aria-label text is concise and matches the platform name.
| <svg fill="none" viewBox="0 0 40 40"><title>social-facebook</title><path className="social-facebook_svg__fb" d="M20 40.004c11.046 0 20-8.955 20-20 0-11.046-8.954-20-20-20s-20 8.954-20 20c0 11.045 8.954 20 20 20z" fill="#1977f3"></path><path d="M27.785 25.785l.886-5.782h-5.546V16.25c0-1.58.773-3.125 3.26-3.125h2.522V8.204s-2.29-.39-4.477-.39c-4.568 0-7.555 2.767-7.555 7.781v4.408h-5.08v5.782h5.08v13.976a20.08 20.08 0 003.125.242c1.063 0 2.107-.085 3.125-.242V25.785h4.66z" fill="#fff"></path></svg> | ||
| </a> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-sm bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | ||
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> |
There was a problem hiding this comment.
Use HTTPS for the LinkedIn share URL.
Line 116 should use https:// instead of http:// to avoid plaintext navigation.
🔐 Suggested fix
- href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`}
+ href={`https://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`}📝 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.
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> | |
| <a className='flex h-[34px] w-14 items-center justify-center rounded-xs bg-muted px-3 hover:bg-muted-foreground/20 [&_svg]:h-4' href={`https://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`} rel="noopener noreferrer" target='_blank'> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/src/components/features/post-share-modal/post-share-modal.tsx` at
line 116, The LinkedIn share anchor in PostShareModal uses an insecure URL
scheme; update the href in the anchor (the <a> element that builds
`http://www.linkedin.com/shareArticle?mini=true&title=${encodedPostTitle}&url=${encodedPostURL}`)
to use `https://www.linkedin.com/...` instead of `http://` so the constructed
share URL uses HTTPS while keeping the existing encodedPostTitle and
encodedPostURL variables unchanged.
| <text | ||
| dy={16} | ||
| fill="hsl(var(--gray-500))" | ||
| fill="var(--gray-500)" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for --gray-500 CSS variable definition
rg -n '\-\-gray-500' --type css
rg -n '\-\-gray-500' apps/shade/Repository: TryGhost/Ghost
Length of output: 139
🏁 Script executed:
# Find and examine styles.css and tailwind config
fd -t f 'styles\.css' apps/shade/
fd -t f 'tailwind\.config\.' apps/shade/Repository: TryGhost/Ghost
Length of output: 110
🏁 Script executed:
# Look at the chart.tsx file around line 372
sed -n '360,385p' apps/shade/src/components/ui/chart.tsxRepository: TryGhost/Ghost
Length of output: 731
🏁 Script executed:
# Read styles.css to see defined CSS variables
cat -n apps/shade/styles.css | head -100Repository: TryGhost/Ghost
Length of output: 4846
🏁 Script executed:
# Check for CSS variable definitions more broadly
rg -n '\-\-' apps/shade/styles.css | head -30Repository: TryGhost/Ghost
Length of output: 1208
🏁 Script executed:
# Check if --muted-foreground exists
rg -n '\-\-muted-foreground' apps/shade/Repository: TryGhost/Ghost
Length of output: 284
🏁 Script executed:
# View tailwind config to understand color setup
head -150 apps/shade/tailwind.config.cjsRepository: TryGhost/Ghost
Length of output: 4922
Replace undefined --gray-500 CSS variable with an existing variable.
The --gray-500 CSS variable does not exist in styles.css. Use var(--muted-foreground) instead, which is defined for both light (hsl(210 13% 55%)) and dark (hsl(210 13% 63%)) modes, making it appropriate for secondary text like axis labels.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/src/components/ui/chart.tsx` at line 372, Replace the nonexistent
CSS variable used for SVG fill in the Chart component: change the fill attribute
currently set to "var(--gray-500)" (used for axis labels/secondary text in
apps/shade/src/components/ui/chart.tsx) to the defined variable
"var(--muted-foreground)"; update the JSX SVG element(s) or any occurrences of
"var(--gray-500)" in chart rendering (e.g., axis label/legend fills) to
"var(--muted-foreground)" so the color resolves in both light and dark modes.
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
Converted !prefix important modifiers to suffix! form and outline-none to outline-hidden across stats, activitypub, and posts apps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The custom borderRadius in tailwind.config.cjs didn't include xs, so rounded-xs used across badges, dropdowns, checkboxes etc. had no effect after the v4 migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/shade/styles.css (1)
117-121:⚠️ Potential issue | 🟡 MinorInvalid
format()descriptor in@font-facedeclaration.The
format()function is only valid after aurl()source, not afterlocal(). Since this is a placeholder that loads from a local font (as the comment indicates), remove the format descriptor.Proposed fix
`@font-face` { font-family: "Inter"; - src: local("Inter") format("truetype-variations"); + src: local("Inter"); font-weight: 100 900; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/shade/styles.css` around lines 117 - 121, The `@font-face` rule for font-family "Inter" uses an invalid format() descriptor after local("Inter"); update the src declaration in that `@font-face` (the rule containing font-family "Inter" and font-weight: 100 900) to remove the format(...) part so the local() source stands alone (e.g., use just local("Inter") or add a proper url(...) source with a format() if you intend to load a file).
🧹 Nitpick comments (2)
apps/shade/styles.css (1)
137-147: Duplicate-webkit-text-size-adjustproperty.
-webkit-text-size-adjust: 100%appears on both line 140 and line 145 within the same.shadeblock.Proposed fix
.shade { line-height: 1.5; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; letter-spacing: unset; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/shade/styles.css` around lines 137 - 147, The .shade CSS rule contains a duplicated vendor property (-webkit-text-size-adjust: 100%); remove the redundant occurrence so the block only declares -webkit-text-size-adjust once (keep a single instance and delete the other) to avoid duplication and keep the .shade selector clean.apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx (1)
82-88: Consider usingKPI_METRICSconstants for consistency.The inline color values duplicate what's already defined in
KPI_METRICS.visits.colorandKPI_METRICS.views.color. Using the constants would ensure consistency if colors are updated.Proposed fix
<KpiDropdownButton> {currentTab === 'visits' && - <KpiTabValue color='var(--chart-blue)' label="Unique visitors" value={kpiValues.visits} /> + <KpiTabValue color={KPI_METRICS.visits.color} label="Unique visitors" value={kpiValues.visits} /> } {currentTab === 'views' && - <KpiTabValue color='var(--chart-teal)' label="Total views" value={kpiValues.views} /> + <KpiTabValue color={KPI_METRICS.views.color} label="Total views" value={kpiValues.views} /> } </KpiDropdownButton>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx` around lines 82 - 88, Replace the hard-coded color strings in the KpiDropdownButton block with the shared constants from KPI_METRICS so updates stay consistent: when rendering KpiTabValue for currentTab === 'visits' use KPI_METRICS.visits.color, and when currentTab === 'views' use KPI_METRICS.views.color; ensure KPI_METRICS is imported/available in this module and update the two KpiTabValue color props accordingly (references: KpiDropdownButton, KpiTabValue, currentTab, kpiValues, KPI_METRICS).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx`:
- Line 301: Remove the unused text sizing classes from the indicator dot
elements in newsletter.tsx: find the small circular divs that use className
values like "ml-0.5 size-[9px] rounded-full bg-chart-purple text-sm! opacity-50
lg:text-base" (and the analogous occurrences at the other two indicator dots)
and delete the text-related classes "text-sm!" and "lg:text-base" so the
className only contains layout/visual classes (e.g., ml-0.5 size-[9px]
rounded-full bg-chart-purple opacity-50).
In `@apps/stats/src/views/Stats/Newsletters/newsletters.tsx`:
- Line 121: The empty-state TableCell currently hardcodes colSpan={5}; change it
to compute the span from the actual column collection used to render the table
(e.g., use the same symbol you define around line 56 such as columns or
visibleColumns) so the span follows the dynamic column count — for example
replace colSpan={5} with colSpan={columns.length} (or
colSpan={visibleColumns.length} / colCount variable that the table uses),
ensuring any extra action/selection column is accounted for if your table adds
phantom columns.
---
Outside diff comments:
In `@apps/shade/styles.css`:
- Around line 117-121: The `@font-face` rule for font-family "Inter" uses an
invalid format() descriptor after local("Inter"); update the src declaration in
that `@font-face` (the rule containing font-family "Inter" and font-weight: 100
900) to remove the format(...) part so the local() source stands alone (e.g.,
use just local("Inter") or add a proper url(...) source with a format() if you
intend to load a file).
---
Nitpick comments:
In `@apps/posts/src/views/PostAnalytics/Web/components/kpis.tsx`:
- Around line 82-88: Replace the hard-coded color strings in the
KpiDropdownButton block with the shared constants from KPI_METRICS so updates
stay consistent: when rendering KpiTabValue for currentTab === 'visits' use
KPI_METRICS.visits.color, and when currentTab === 'views' use
KPI_METRICS.views.color; ensure KPI_METRICS is imported/available in this module
and update the two KpiTabValue color props accordingly (references:
KpiDropdownButton, KpiTabValue, currentTab, kpiValues, KPI_METRICS).
In `@apps/shade/styles.css`:
- Around line 137-147: The .shade CSS rule contains a duplicated vendor property
(-webkit-text-size-adjust: 100%); remove the redundant occurrence so the block
only declares -webkit-text-size-adjust once (keep a single instance and delete
the other) to avoid duplication and keep the .shade selector clean.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
apps/activitypub/package.jsonapps/activitypub/src/components/feed/table-of-contents.tsxapps/activitypub/src/components/global/ap-avatar.tsxapps/activitypub/src/components/global/image-lightbox.tsxapps/activitypub/src/components/global/profile-preview-hover-card.tsxapps/activitypub/src/components/layout/header/header.tsxapps/activitypub/src/components/layout/onboarding/step-1.tsxapps/activitypub/src/components/layout/onboarding/step-2.tsxapps/activitypub/src/components/modals/new-note-modal.tsxapps/activitypub/src/views/feed/components/suggested-profiles.tsxapps/activitypub/src/views/inbox/components/customizer.tsxapps/activitypub/src/views/inbox/components/inbox-list.tsxapps/activitypub/src/views/inbox/components/reader.tsxapps/activitypub/src/views/notifications/notifications.tsxapps/activitypub/src/views/preferences/components/bluesky-sharing.tsxapps/activitypub/src/views/preferences/components/edit-profile.tsxapps/activitypub/src/views/preferences/components/moderation.tsxapps/activitypub/src/views/preferences/components/profile.tsxapps/posts/src/components/member-avatar.tsxapps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsxapps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsxapps/posts/src/views/PostAnalytics/Web/components/kpis.tsxapps/posts/src/views/PostAnalytics/components/kpi-card.tsxapps/posts/src/views/Tags/components/tags-list.tsxapps/shade/styles.cssapps/shade/tailwind.config.cjsapps/stats/src/views/Stats/Growth/components/growth-kpis.tsxapps/stats/src/views/Stats/Growth/components/growth-sources.tsxapps/stats/src/views/Stats/Growth/components/paid-subscription-change-chart.tsxapps/stats/src/views/Stats/Growth/growth.tsxapps/stats/src/views/Stats/Newsletters/components/newsletters-kpis.tsxapps/stats/src/views/Stats/Newsletters/newsletters.tsxapps/stats/src/views/Stats/Overview/components/latest-post.tsxapps/stats/src/views/Stats/Overview/components/overview-kpis.tsxapps/stats/src/views/Stats/Overview/components/top-posts.tsxapps/stats/src/views/Stats/Web/components/web-kpis.tsxapps/stats/src/views/Stats/layout/stats-header.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- apps/stats/src/views/Stats/Growth/components/paid-subscription-change-chart.tsx
- apps/stats/src/views/Stats/Newsletters/components/newsletters-kpis.tsx
- apps/stats/src/views/Stats/Overview/components/overview-kpis.tsx
- apps/stats/src/views/Stats/Growth/components/growth-kpis.tsx
- apps/stats/src/views/Stats/Web/components/web-kpis.tsx
| navigate(`/members?${params.toString()}`, {crossApp: true}); | ||
| }}> | ||
| <div className='ml-0.5 size-[9px] rounded-full bg-chart-purple !text-sm opacity-50 lg:text-base'></div> | ||
| <div className='ml-0.5 size-[9px] rounded-full bg-chart-purple text-sm! opacity-50 lg:text-base'></div> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check if the file exists and get the relevant lines
file_path="apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx"
if [ -f "$file_path" ]; then
echo "=== Context around line 301 ==="
sed -n '295,310p' "$file_path"
echo ""
echo "=== Context around line 327 ==="
sed -n '320,335p' "$file_path"
echo ""
echo "=== Context around line 354 ==="
sed -n '348,360p' "$file_path"
else
echo "File not found"
fiRepository: TryGhost/Ghost
Length of output: 3573
🏁 Script executed:
#!/bin/bash
# Search for similar patterns: text-sm on non-text elements (size indicators, dots, etc.)
echo "=== Searching for size-[9px] with text-sm ==="
rg -n 'size-\[9px\].*text-sm' --type=tsx --type=ts
echo ""
echo "=== Searching for rounded-full with text-sm ==="
rg -n 'rounded-full.*text-sm' --type=tsx --type=ts | head -20
echo ""
echo "=== Broader search: text-sm on elements that might not need text styling ==="
rg -n 'size-\[.*\].*text-sm' --type=tsx --type=ts | head -15Repository: TryGhost/Ghost
Length of output: 331
🏁 Script executed:
#!/bin/bash
# Check if text-sm! is used elsewhere in the codebase
echo "=== All occurrences of text-sm! in the codebase ==="
rg -n 'text-sm!' --type=tsx --type=ts | head -20Repository: TryGhost/Ghost
Length of output: 154
🏁 Script executed:
#!/bin/bash
# Search for text-sm patterns without type filters
echo "=== Searching for text-sm! in the entire codebase ==="
rg 'text-sm!' | head -30
echo ""
echo "=== Searching for rounded-full AND text-sm patterns ==="
rg 'rounded-full.*text-sm' | head -20Repository: TryGhost/Ghost
Length of output: 2136
🏁 Script executed:
#!/bin/bash
# Check git history to see if this class was intentionally added
cd apps/posts/src/views/PostAnalytics/Newsletter
if [ -f "newsletter.tsx" ]; then
echo "=== Git blame for line 301 area ==="
git blame -L 295,305 newsletter.tsx 2>/dev/null | head -15
fiRepository: TryGhost/Ghost
Length of output: 1588
🏁 Script executed:
#!/bin/bash
# Search for other indicator dots in the codebase to see if they use text-sm
rg 'size-\[' apps/posts/src/views/PostAnalytics/ --type tsx -A 1 -B 1 | head -50Repository: TryGhost/Ghost
Length of output: 85
Remove unused text sizing classes from indicator dots.
The text-sm! and lg:text-base classes on the 9px indicator divs (lines 301, 327, 354) have no visual effect because these elements contain no text. Text sizing classes only affect font rendering, not empty elements with fixed dimensions. These classes appear to be vestigial and should be removed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` at line 301,
Remove the unused text sizing classes from the indicator dot elements in
newsletter.tsx: find the small circular divs that use className values like
"ml-0.5 size-[9px] rounded-full bg-chart-purple text-sm! opacity-50
lg:text-base" (and the analogous occurrences at the other two indicator dots)
and delete the text-related classes "text-sm!" and "lg:text-base" so the
className only contains layout/visual classes (e.g., ml-0.5 size-[9px]
rounded-full bg-chart-purple opacity-50).
| : | ||
| <TableRow className='border-none hover:bg-transparent'> | ||
| <TableCell className='text-center group-hover:!bg-transparent' colSpan={5}> | ||
| <TableCell className='text-center group-hover:bg-transparent!' colSpan={5}> |
There was a problem hiding this comment.
Use dynamic colSpan in empty state row.
Line 121 hardcodes colSpan={5}, but column count is dynamic (see Line 56). This can misalign table semantics/layout when opens or clicks tracking is disabled.
Suggested fix
- <TableCell className='text-center group-hover:bg-transparent!' colSpan={5}>
+ <TableCell className='text-center group-hover:bg-transparent!' colSpan={colSpan}>📝 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.
| <TableCell className='text-center group-hover:bg-transparent!' colSpan={5}> | |
| <TableCell className='text-center group-hover:bg-transparent!' colSpan={colSpan}> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/stats/src/views/Stats/Newsletters/newsletters.tsx` at line 121, The
empty-state TableCell currently hardcodes colSpan={5}; change it to compute the
span from the actual column collection used to render the table (e.g., use the
same symbol you define around line 56 such as columns or visibleColumns) so the
span follows the dynamic column count — for example replace colSpan={5} with
colSpan={columns.length} (or colSpan={visibleColumns.length} / colCount variable
that the table uses), ensuring any extra action/selection column is accounted
for if your table adds phantom columns.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/shade/tailwind.config.cjs`:
- Line 356: The xs size for the rounded-xs token uses calc(var(--radius) - 6px)
which can become negative when --radius < 6px; update the xs value in
tailwind.config.cjs (the xs key that backs the rounded-xs utility) to guard
against negatives by wrapping the computation with a CSS max() or clamp() so the
final value never drops below 0px (e.g., ensure the expression yields at least
0px); change the xs entry so rounded-xs will no-op safely instead of producing
an invalid negative radius.
- Line 469: Restore the tailwindcss-animate plugin in the Tailwind config by
adding it back into the plugins array (the plugins entry in tailwind.config.cjs)
so existing animation utilities like animate-in/animate-out, fade-in-0,
zoom-in-95, slide-in-from-*, etc. continue to work; update the plugins array to
include require("tailwindcss-animate") (or the project’s plugin import) rather
than leaving plugins: [], ensuring components that reference those classes
(hover-card, tooltip, sheet, popover, select, dropdown-menu, dialog,
alert-dialog) keep their animations.
- Line 4: The small-radius value defined as xs: 'calc(var(--radius) - 6px)' can
produce negative/invalid CSS when --radius is under 6px; locate the xs key in
theme.extend (the radius/borderRadius definition where xs is set) and change its
value to guard against negatives, e.g. replace the raw calc with a max() guard
such as max(calc(var(--radius) - 6px), 0.2rem) (or another sensible minimum) so
the computed radius never becomes invalid.
| module.exports = { | ||
| content: ['./src/**/*.{js,ts,jsx,tsx}'], | ||
| important: '.shade', | ||
| important: false, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Content of apps/shade/tailwind.config.cjs ==="
cat -n apps/shade/tailwind.config.cjs
echo
echo "=== Check for tailwind.cjs files ==="
find . -name "tailwind.cjs" -o -name "tailwind.js" | head -20Repository: TryGhost/Ghost
Length of output: 20099
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for 'important' configuration in config files ==="
rg -n "important\s*[:=]" --type=js --type=cjs -A2 -B2 apps/shade/
echo
echo "=== Search for '.shade' class and scoping patterns ==="
rg -n "\.shade|shade.*important" --type=js --type=cjs --type=ts -B2 -A2
echo
echo "=== Search for tailwind.cjs wiring in config or build files ==="
rg -n "tailwind\.cjs" -B2 -A2 --type=js --type=cjs --type=tsRepository: TryGhost/Ghost
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Check for animate plugin usage in Shade ==="
rg -n "@tailwindcss/animate|animate-|duration-|delay-" apps/shade/ --type=js --type=ts --type=html --type=tsx --type=jsx 2>/dev/null | head -30
echo
echo "=== Check git history for previous important value ==="
git log -p --all -S "important" -- apps/shade/tailwind.config.cjs | head -100Repository: TryGhost/Ghost
Length of output: 105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Content of apps/shade/tailwind.cjs ==="
cat -n apps/shade/tailwind.cjs | head -150
echo
echo "=== Check for .shade selector in tailwind.cjs ==="
grep -n "\.shade\|important" apps/shade/tailwind.cjs || echo "No matches found"Repository: TryGhost/Ghost
Length of output: 410
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for animate class usage in apps/shade/src ==="
find apps/shade/src -type f \( -name "*.tsx" -o -name "*.ts" -o -name "*.jsx" -o -name "*.js" \) -exec grep -l "animate-\|duration-\|delay-" {} \; 2>/dev/null | head -20
echo
echo "=== Check git log for important configuration history ==="
cd apps/shade && git log --oneline -n 20 -- tailwind.config.cjs 2>/dev/null || echo "Git history not available"Repository: TryGhost/Ghost
Length of output: 1020
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Git show for previous version of important setting ==="
git show HEAD~1:apps/shade/tailwind.config.cjs 2>/dev/null | grep -A2 -B2 "important" || echo "Previous version not found"Repository: TryGhost/Ghost
Length of output: 143
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for standard Tailwind animate utilities (animate-bounce, animate-ping, etc.) ==="
rg "animate-(bounce|ping|pulse|spin)" apps/shade/src/ --type=ts --type=tsx --type=js --type=jsx 2>/dev/null | head -20
echo
echo "=== Search for classes in HTML/JSX to double-check ==="
rg 'className=.*animate-' apps/shade/src/ --type=ts --type=tsx --type=jsx 2>/dev/null | head -20Repository: TryGhost/Ghost
Length of output: 149
Scoping behavior is correctly preserved through the tailwind.cjs wrapper function.
The important: false setting is intentional. Per the file comment, tailwind.config.cjs is used only for eslint and storybook and should not be compiled directly. The scoping delegation is handled by tailwind.cjs, which exports a function that accepts a selector and sets important: selector when called during the actual build. This maintains the intended .shade scoping behavior.
The plugins: [] removal is correct—your animations are custom-defined in theme.extend.animation and don't require the @tailwindcss/animate plugin.
One remaining concern: Line 356's xs: 'calc(var(--radius) - 6px)' can produce invalid values if --radius < 6px. Consider adding a CSS max() guard, e.g., xs: 'max(calc(var(--radius) - 6px), 0.2rem)'.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/tailwind.config.cjs` at line 4, The small-radius value defined as
xs: 'calc(var(--radius) - 6px)' can produce negative/invalid CSS when --radius
is under 6px; locate the xs key in theme.extend (the radius/borderRadius
definition where xs is set) and change its value to guard against negatives,
e.g. replace the raw calc with a max() guard such as max(calc(var(--radius) -
6px), 0.2rem) (or another sensible minimum) so the computed radius never becomes
invalid.
| pageminsidebar: '116rem' | ||
| }, | ||
| borderRadius: { | ||
| xs: 'calc(var(--radius) - 6px)', |
There was a problem hiding this comment.
Guard rounded-xs against invalid negative radii.
calc(var(--radius) - 6px) is invalid when --radius is below 6px, which makes rounded-xs silently no-op in those themes.
Suggested hardening
- xs: 'calc(var(--radius) - 6px)',
+ xs: 'max(calc(var(--radius) - 6px), 0px)',📝 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.
| xs: 'calc(var(--radius) - 6px)', | |
| xs: 'max(calc(var(--radius) - 6px), 0px)', |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/tailwind.config.cjs` at line 356, The xs size for the rounded-xs
token uses calc(var(--radius) - 6px) which can become negative when --radius <
6px; update the xs value in tailwind.config.cjs (the xs key that backs the
rounded-xs utility) to guard against negatives by wrapping the computation with
a CSS max() or clamp() so the final value never drops below 0px (e.g., ensure
the expression yields at least 0px); change the xs entry so rounded-xs will
no-op safely instead of producing an invalid negative radius.
| } | ||
| }, | ||
| plugins: [require('tailwindcss-animate')] | ||
| plugins: [] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Potential tailwindcss-animate utility usage in Shade =="
rg -nP --type=js --type=jsx --type=ts --type=tsx --type=html --type=mdx -C1 \
'\b(animate-(in|out)|fade-(in|out)(-[0-9]+)?|zoom-(in|out)(-[0-9]+)?|slide-(in|out)-from-[a-z-]+|flip-(in|out)-[xy]|repeat-(0|1|infinite)|direction-(normal|reverse|alternate)|running|paused)\b' \
apps/shade/srcRepository: TryGhost/Ghost
Length of output: 161
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Potential tailwindcss-animate utility usage in Shade =="
rg -nP --type=js --type=ts --type=html -C1 \
'\b(animate-(in|out)|fade-(in|out)(-[0-9]+)?|zoom-(in|out)(-[0-9]+)?|slide-(in|out)-from-[a-z-]+|flip-(in|out)-[xy]|repeat-(0|1|infinite)|direction-(normal|reverse|alternate)|running|paused)\b' \
apps/shade/src 2>&1 || true
# Also search for .jsx, .tsx files without type filter
echo ""
echo "== Searching .jsx/.tsx files directly =="
rg -nP -C1 \
'\b(animate-(in|out)|fade-(in|out)(-[0-9]+)?|zoom-(in|out)(-[0-9]+)?|slide-(in|out)-from-[a-z-]+|flip-(in|out)-[xy]|repeat-(0|1|infinite)|direction-(normal|reverse|alternate)|running|paused)\b' \
--glob='*.{jsx,tsx}' \
apps/shade/src 2>&1 || echo "No matches found"Repository: TryGhost/Ghost
Length of output: 16524
Do not remove tailwindcss-animate plugin—extensive animation utilities are still in use throughout Shade components.
Multiple UI components use animation classes (animate-in, animate-out, fade-in-0, zoom-in-95, slide-in-from-*, etc.):
apps/shade/src/components/ui/hover-card.tsxapps/shade/src/components/ui/tooltip.tsxapps/shade/src/components/ui/sheet.tsxapps/shade/src/components/ui/popover.tsxapps/shade/src/components/ui/select.tsxapps/shade/src/components/ui/dropdown-menu.tsxapps/shade/src/components/ui/dialog.tsxapps/shade/src/components/ui/alert-dialog.tsx
Removing the plugin will break animations in dialogs, dropdowns, sheets, and popovers. Either restore the plugin or remove all animation utility classes from these components.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/shade/tailwind.config.cjs` at line 469, Restore the tailwindcss-animate
plugin in the Tailwind config by adding it back into the plugins array (the
plugins entry in tailwind.config.cjs) so existing animation utilities like
animate-in/animate-out, fade-in-0, zoom-in-95, slide-in-from-*, etc. continue to
work; update the plugins array to include require("tailwindcss-animate") (or the
project’s plugin import) rather than leaving plugins: [], ensuring components
that reference those classes (hover-card, tooltip, sheet, popover, select,
dropdown-menu, dialog, alert-dialog) keep their animations.
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
- Converted ~85 `!prefix` important modifiers to `suffix!` format across admin-x-design-system (17 files) and admin-x-settings (30 files) - Converted 6 `outline-none` to `outline-hidden` (preserves v3 behavior) - Converted `flex-grow` to `grow` in slack-modal.tsx - Fixed `bg-accent`/`text-accent` to `bg-ghostaccent`/`text-ghostaccent` in tier-detail-preview.tsx (accent color semantic mismatch between design system and Shade configs) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx (1)
242-242: Extract duplicated Test button className into a shared constant.Both updated button class strings are identical; centralizing them will reduce drift in future Tailwind migrations.
♻️ Proposed refactor
const WelcomeEmailModal = NiceModal.create<WelcomeEmailModalProps>(({emailType = 'free', automatedEmail}) => { + const testButtonClassName = 'border border-grey-200 font-semibold hover:border-grey-300 hover:bg-white! dark:border-grey-900 dark:hover:border-grey-800 dark:hover:bg-grey-950!'; const modal = useModal(); @@ - <LegacyButton - className='border border-grey-200 font-semibold hover:border-grey-300 hover:bg-white! dark:border-grey-900 dark:hover:border-grey-800 dark:hover:bg-grey-950!' + <LegacyButton + className={testButtonClassName} color="clear" icon='send' label="Test" @@ - <LegacyButton - className='border border-grey-200 font-semibold hover:border-grey-300 hover:bg-white! dark:border-grey-900 dark:hover:border-grey-800 dark:hover:bg-grey-950!' + <LegacyButton + className={testButtonClassName} color="clear" icon='send' label="Test"Also applies to: 356-356
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx` at line 242, Two identical Tailwind className strings used for the "Test" button in the WelcomeEmailModal are duplicated; extract that string into a shared constant (e.g. TEST_BUTTON_CLASS) at the top of the welcome-email-modal.tsx module and replace both inline className usages with that constant (references: the Test button JSX in WelcomeEmailModal — update both occurrences that currently use 'border border-grey-200 font-semibold hover:border-grey-300 hover:bg-white! dark:border-grey-900 dark:hover:border-grey-800 dark:hover:bg-grey-950!').
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@apps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsx`:
- Line 242: Two identical Tailwind className strings used for the "Test" button
in the WelcomeEmailModal are duplicated; extract that string into a shared
constant (e.g. TEST_BUTTON_CLASS) at the top of the welcome-email-modal.tsx
module and replace both inline className usages with that constant (references:
the Test button JSX in WelcomeEmailModal — update both occurrences that
currently use 'border border-grey-200 font-semibold hover:border-grey-300
hover:bg-white! dark:border-grey-900 dark:hover:border-grey-800
dark:hover:bg-grey-950!').
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
apps/admin-x-design-system/src/global/button-group.tsxapps/admin-x-design-system/src/global/button.tsxapps/admin-x-design-system/src/global/form/checkbox.tsxapps/admin-x-design-system/src/global/form/code-editor-view.tsxapps/admin-x-design-system/src/global/form/image-upload.tsxapps/admin-x-design-system/src/global/form/koenig-editor-base.tsxapps/admin-x-design-system/src/global/form/multi-select.tsxapps/admin-x-design-system/src/global/form/radio.tsxapps/admin-x-design-system/src/global/form/select.tsxapps/admin-x-design-system/src/global/layout/page.stories.tsxapps/admin-x-design-system/src/global/modal/modal.tsxapps/admin-x-design-system/src/global/modal/preview-modal.tsxapps/admin-x-design-system/src/global/popover.tsxapps/admin-x-design-system/src/global/sort-menu.tsxapps/admin-x-design-system/src/global/table-cell.tsxapps/admin-x-design-system/src/global/table-head.tsxapps/admin-x-design-system/src/settings/setting-group-header.tsxapps/admin-x-settings/src/components/exit-settings-button.tsxapps/admin-x-settings/src/components/settings/advanced/integrations/slack-modal.tsxapps/admin-x-settings/src/components/settings/advanced/integrations/transistor-modal.tsxapps/admin-x-settings/src/components/settings/advanced/migration-tools/migration-tools-export.tsxapps/admin-x-settings/src/components/settings/email/enable-newsletters.tsxapps/admin-x-settings/src/components/settings/email/newsletters/newsletter-detail-modal.tsxapps/admin-x-settings/src/components/settings/email/newsletters/newsletters-list.tsxapps/admin-x-settings/src/components/settings/general/user-detail-modal.tsxapps/admin-x-settings/src/components/settings/growth/embed-signup/embed-signup-sidebar.tsxapps/admin-x-settings/src/components/settings/growth/explore.tsxapps/admin-x-settings/src/components/settings/growth/explore/testimonials-modal.tsxapps/admin-x-settings/src/components/settings/growth/offers/offer-success.tsxapps/admin-x-settings/src/components/settings/growth/recommendations.tsxapps/admin-x-settings/src/components/settings/growth/recommendations/incoming-recommendation-list.tsxapps/admin-x-settings/src/components/settings/growth/recommendations/recommendation-list.tsxapps/admin-x-settings/src/components/settings/membership/analytics.tsxapps/admin-x-settings/src/components/settings/membership/member-emails.tsxapps/admin-x-settings/src/components/settings/membership/member-emails/member-email-editor.tsxapps/admin-x-settings/src/components/settings/membership/member-emails/test-email-dropdown.tsxapps/admin-x-settings/src/components/settings/membership/member-emails/welcome-email-modal.tsxapps/admin-x-settings/src/components/settings/membership/portal.tsxapps/admin-x-settings/src/components/settings/membership/portal/look-and-feel.tsxapps/admin-x-settings/src/components/settings/membership/stripe/stripe-connect-modal.tsxapps/admin-x-settings/src/components/settings/membership/tiers.tsxapps/admin-x-settings/src/components/settings/membership/tiers/tier-detail-modal.tsxapps/admin-x-settings/src/components/settings/membership/tiers/tier-detail-preview.tsxapps/admin-x-settings/src/components/settings/site/design-and-branding/global-settings.tsxapps/admin-x-settings/src/components/settings/site/theme-modal.tsxapps/admin-x-settings/src/components/settings/site/theme/theme-preview.tsxapps/admin-x-settings/src/components/sidebar.tsx
✅ Files skipped from review due to trivial changes (3)
- apps/admin-x-settings/src/components/settings/membership/stripe/stripe-connect-modal.tsx
- apps/admin-x-settings/src/components/settings/membership/analytics.tsx
- apps/admin-x-settings/src/components/settings/membership/member-emails/test-email-dropdown.tsx
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
fixes DES-1301 When settings lazy-loaded, kg-unsplash-selector's bundled TW v3 CSS was injected as a <style> tag after the admin's TW v4 CSS. The v3 `.fixed` utility overrode v4's `tablet:relative` responsive variant due to cascade order, causing the sidebar to stay fixed and breaking the content layout. Fix: removed the direct CSS import from unsplash-selector.tsx and added a @source directive so TW v4 generates the needed utilities from kg-unsplash-selector's built JS instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Velo CI Failure AnalysisClassification: ⚪ CI FAIL
|
Documents the TailwindCSS v4 setup, centralized CSS pipeline, Shade styles, embedded app rules, and public/legacy app status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The @tailwindcss/vite plugin types conflicted with Vite 7 types due to duplicate Vite versions in the monorepo. Cast as PluginOption to resolve. Also ran eslint --fix for tailwindcss/classnames-order across all affected packages after v4 syntax changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
TK