Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,69 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

---

## [`2.0.1`] - 2026-03-29
## [`2.0.2`] - 2026-03-30

**Citations look like they belong in a paper now, and mobile stopped pretending it's desktop.** BibTeX gets syntax highlighting everywhere it appears, the search button actually works on phones, and a handful of layout rough edges got sanded down.

### Added

- **BibTeX syntax highlighting** - entry types, field names, and delimiters get distinct colors in both the article cite panel and footer citation block. Shared via `formatBibtexHtml` in `src/lib/citation.ts` so the two never drift.
- **Citation format crossfade** - switching between BibTeX / APA / Markdown fades the content out and back in instead of hard-swapping. Copy button tooltip confirms which format was copied.
- **Mobile search integration** - the nav search button now opens the filter bottom sheet with the search input focused (dispatches `open-mobile-search` event). Keyboard shortcut `/` does the same. Closes the mobile nav first if it's open.
- **Mobile FAB auto-hide** - the floating "Filters" button fades out when the footer scrolls into view, so it stops covering the citation block on short pages
- **E2E test helpers module** - `isMobileProject`, `waitForNextAstroPageLoad`, `setTheme`, `trackConsoleErrors` extracted to `tests/e2e/helpers.ts` and shared across specs

### Changed

- Engagement bar stacks cleanly on mobile: Share + Cite stay grouped in a row, feedback sits above, separators hidden
- Share dropdown centers itself on mobile viewports instead of overflowing off-screen
- Feedback follow-up text animates in with opacity transition instead of a `display: none` toggle
- CatalogHero trust badge uses `text-wrap: balance` and wraps the text in a `<span>` so the book icon doesn't reflow into the second line
- BrowseNav wraps gracefully on small screens (dots hidden)
- Footer copyright name now links to author profile
- Accessibility axe-core tests marked `test.slow()` to stop flaking under load

### Fixed

- Article content overflowing its container on narrow viewports (`min-width: 0` on the content column)
- 404 suggestion link navigation failing in webkit - uses `force: true` click with parallel `waitForURL`
- Analytics consent test race condition on Astro navigation - now awaits `astro:page-load` event before asserting page view count
- Console error filtering in 404 tests - expected 404 resource errors no longer pollute the error list

---

## [`2.0.1`] - 2026-03-30

**Users can now quiet animations without touching their OS settings.** A sparkles toggle in the nav gives per-site control, persisted across sessions.

### Added

- **Animation toggle** sparkles button in the nav lets users reduce motion on this site alone. Bounces when sparkles come back to life; goes instantly quiet when they don't.
- **Tooltip system** for nav icon buttons CSS-only `data-tooltip` with arrow, hover delay, and keyboard focus support, replacing native `title` attributes
- **Motion contract** (`motion-contract.ts`) shared constants and types mirroring the theme system architecture
- **Motion preference blocking script** resolves `html[data-motion]` before first paint (localStorage first, OS `prefers-reduced-motion` as fallback), stamps new documents on view transitions
- **Animation toggle** - sparkles button in the nav lets users reduce motion on this site alone. Bounces when sparkles come back to life; goes instantly quiet when they don't.
- **Tooltip system** for nav icon buttons - CSS-only `data-tooltip` with arrow, hover delay, and keyboard focus support, replacing native `title` attributes
- **Motion contract** (`motion-contract.ts`) - shared constants and types mirroring the theme system architecture
- **Motion preference blocking script** - resolves `html[data-motion]` before first paint (localStorage first, OS `prefers-reduced-motion` as fallback), stamps new documents on view transitions

### Changed

- Motion system rewired from fifteen independent `@media (prefers-reduced-motion)` blocks to one `html[data-motion]` attribute driving a global CSS kill switch near-zero durations on all transitions, animations, and view transitions when reduced
- Motion system rewired from fifteen independent `@media (prefers-reduced-motion)` blocks to one `html[data-motion]` attribute driving a global CSS kill switch - near-zero durations on all transitions, animations, and view transitions when reduced
- Mobile nav hardened against view-transition DOM replacement: lazy element lookups, re-bound listeners on `astro:page-load`, duplicate-event guards via data attributes
- E2E test helpers extracted for consent flow, catalog filter scoping, and stable click-with-scroll patterns; clipboard tests skip non-Chromium; force-click fallbacks for mobile viewports

### Fixed

- Horizontal overflow from `left: -100vw; right: -100vw` on full-bleed decorative backgrounds in CatalogHero and ArticleLayout replaced with `translateX(-50%)` centering
- ThemeToggle spin animation clipping tooltip pseudo-elements now targets inner icons instead of the button, `overflow: hidden` dropped
- Horizontal overflow from `left: -100vw; right: -100vw` on full-bleed decorative backgrounds in CatalogHero and ArticleLayout - replaced with `translateX(-50%)` centering
- ThemeToggle spin animation clipping tooltip pseudo-elements - now targets inner icons instead of the button, `overflow: hidden` dropped

---

## [`2.0.0`] - 2026-03-23

**Complete rewrite Gatsby → Astro 5.** New architecture, new design, same 56 smells.
**Complete rewrite - Gatsby → Astro 5.** New architecture, new design, same 56 smells.

### Architecture

- Migrated from Gatsby (React, Material UI) to **Astro 5** with static output
- Adopted **Preact islands** for interactive components (FilterSidebar, CodeExample) most pages ship zero framework JS
- Adopted **Preact islands** for interactive components (FilterSidebar, CodeExample) - most pages ship zero framework JS
- Replaced client-side state with **Nano Stores** (shared between islands and vanilla scripts)
- Switched styling from Material UI to **Tailwind CSS v4** (via `@tailwindcss/vite`)
- Self-hosted fonts via **Fontsource** (Fraunces, Plus Jakarta Sans, JetBrains Mono)
Expand Down Expand Up @@ -351,6 +382,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
[`1.0.20`]: https://github.com/Luzkan/smells/releases/tag/1.0.20
[`1.0.21`]: https://github.com/Luzkan/smells/releases/tag/1.0.21
[`1.0.22`]: https://github.com/Luzkan/smells/releases/tag/1.0.22
[`2.0.2`]: https://github.com/Luzkan/smells/releases/tag/2.0.2
[`2.0.1`]: https://github.com/Luzkan/smells/releases/tag/2.0.1
[`2.0.0`]: https://github.com/Luzkan/smells/releases/tag/2.0.0
[`1.0.23-alpha.1`]: https://github.com/Luzkan/smells/releases/tag/1.0.23-alpha.1
24 changes: 19 additions & 5 deletions src/components/article/EngagementBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ const { citation } = Astro.props;
<div class="engagement-bar">
<FeedbackButton slug={citation.slug} />
<span class="engage-sep"></span>
<ShareButton slug={citation.slug} />
<span class="engage-sep"></span>
<CiteToggle />
<div class="engage-actions">
<ShareButton slug={citation.slug} />
<span class="engage-sep"></span>
<CiteToggle />
</div>
</div>
<CitePanel citation={citation} />

Expand All @@ -34,6 +36,10 @@ const { citation } = Astro.props;
margin-bottom: 0;
}

.engage-actions {
display: contents;
}

.engage-sep {
width: 1px;
height: 24px;
Expand Down Expand Up @@ -71,13 +77,21 @@ const { citation } = Astro.props;
@media (max-width: 640px) {
.engagement-bar {
flex-direction: column;
align-items: center;
padding: 16px 20px;
gap: 12px;
margin-bottom: 16px;
}

.engage-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
}

.engage-sep {
width: 100%;
height: 1px;
display: none;
}
}
</style>
14 changes: 14 additions & 0 deletions src/components/catalog/BrowseNav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,19 @@ const { totalSmells, allSlugs, currentSlug } = Astro.props;
}
}

@media (max-width: 640px) {
.browse-nav {
flex-wrap: wrap;
}

.browse-nav__all {
padding: 8px 18px;
}

.browse-nav__dot {
display: none;
}
}

/* diceShake keyframes defined globally in global.css */
</style>
4 changes: 3 additions & 1 deletion src/components/catalog/CatalogHero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ const { totalCount, categoryCount, hierarchyCount } = Astro.props;
{/* HERO-05: Springer Nature trust badge */}
<a class="catalog-hero__trust" href={SPRINGER_PAPER_URL} target="_blank" rel="noopener">
<BookIcon class="catalog-hero__trust-icon" size={14} strokeWidth={2} />
Companion to peer-reviewed research published by <strong>Springer Nature</strong>
{/* Wrapper <span> prevents the icon from reflowing into wrapped text on small screens */}
<span>Companion to peer-reviewed research published by <strong>Springer Nature</strong></span>
</a>
</section>

Expand Down Expand Up @@ -190,6 +191,7 @@ const { totalCount, categoryCount, hierarchyCount } = Astro.props;
background: var(--surface);
transition: all 0.25s var(--ease-smooth);
text-decoration: none;
text-wrap: balance;
}

.catalog-hero__trust:hover {
Expand Down
73 changes: 54 additions & 19 deletions src/components/engagement/CitePanel.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ interface Props {
import Icon from '../icons/Icon.astro';
import { ICON_COPY } from '../../lib/icon-paths';
import { SITE_URL } from '../../lib/constants';
import { buildArticleBibtex, formatBibtexHtml } from '../../lib/citation';

const { citation } = Astro.props;
const { slug, title, author, year } = citation;
const url = `${SITE_URL}/smells/${slug}`;
const bibtexRaw = buildArticleBibtex(slug, title, author, year, url);
const bibtexHtml = formatBibtexHtml(bibtexRaw);
---

<div class="cite-panel" id="cite-panel" data-slug={slug} aria-hidden="true" inert>
Expand All @@ -25,11 +28,11 @@ const url = `${SITE_URL}/smells/${slug}`;
</div>
<div class="cite-block">
<pre
class="cite-block__code"
data-bibtex={`@misc{jerzyk${year}${slug.replace(/-/g, '')},\n title = {${title} — Code Smells Catalog},\n author = {${author}},\n year = {${year}},\n url = {${url}}\n}`}
class="cite-bib-block"
data-bibtex={bibtexRaw}
data-bibtex-html={bibtexHtml}
data-apa={`${author} (${year}). ${title} — Code Smells Catalog. ${url}`}
data-markdown={`[${title}](${url}) — *Code Smells Catalog* by ${author} (${year})`}>
</pre>
data-markdown={`[${title}](${url}) — *Code Smells Catalog* by ${author} (${year})`}><code class="cite-block__code" /></pre>
<button class="cite-copy-btn" title="Copy citation" aria-label="Copy citation">
<Icon icon={ICON_COPY} size={12} />
</button>
Expand All @@ -39,7 +42,7 @@ const url = `${SITE_URL}/smells/${slug}`;

<script>
import { trackEvent } from '../../lib/analytics/tracker';
import { copyToClipboardWithFeedback } from '../../lib/copy-code';
import { copyToClipboardWithFeedback, COPY_FEEDBACK_MS } from '../../lib/copy-code';
import { initOnce } from '../../lib/lifecycle';

function toCiteFormat(value: string | null): 'bibtex' | 'apa' | 'markdown' {
Expand All @@ -49,10 +52,24 @@ const url = `${SITE_URL}/smells/${slug}`;

initOnce('.cite-panel', (citePanel) => {
const slug = citePanel.getAttribute('data-slug') || '';
const citeBlock = citePanel.querySelector('.cite-block__code');
if (!(citeBlock instanceof HTMLElement)) return;
const citePre = citePanel.querySelector('.cite-bib-block');
const citeCodeEl = citePanel.querySelector('.cite-block__code');
if (!(citePre instanceof HTMLElement) || !(citeCodeEl instanceof HTMLElement)) return;

const pre = citePre;
const code = citeCodeEl;
let fadeRaf: number | null = null;
function crossfade(updateFn: () => void): void {
if (fadeRaf !== null) cancelAnimationFrame(fadeRaf);
code.classList.add('cite-block__code--fading');
fadeRaf = requestAnimationFrame(() => {
updateFn();
code.classList.remove('cite-block__code--fading');
fadeRaf = null;
});
}

citeBlock.textContent = citeBlock.getAttribute('data-bibtex') || '';
code.innerHTML = pre.getAttribute('data-bibtex-html') || '';

const tabs = citePanel.querySelectorAll('.cite-tab');
tabs.forEach((tab) => {
Expand All @@ -61,15 +78,21 @@ const url = `${SITE_URL}/smells/${slug}`;
tabs.forEach((candidate) => candidate.classList.remove('cite-tab--active'));
tab.classList.add('cite-tab--active');
const format = toCiteFormat(tab.getAttribute('data-format'));
citeBlock.textContent = citeBlock.getAttribute(`data-${format}`) || '';
crossfade(() => {
if (format === 'bibtex') {
code.innerHTML = pre.getAttribute('data-bibtex-html') || '';
} else {
code.textContent = pre.getAttribute(`data-${format}`) || '';
}
});
});
});

const copyButton = citePanel.querySelector('.cite-copy-btn');
if (!(copyButton instanceof HTMLButtonElement)) return;

copyButton.addEventListener('click', async () => {
const copied = await copyToClipboardWithFeedback(citeBlock.textContent || '', copyButton, {
const copied = await copyToClipboardWithFeedback(code.textContent || '', copyButton, {
feedbackClass: 'cite-copy-btn--copied',
});
if (!copied) return;
Expand All @@ -78,6 +101,12 @@ const url = `${SITE_URL}/smells/${slug}`;
const format = toCiteFormat(
activeTab instanceof HTMLElement ? activeTab.getAttribute('data-format') : null,
);
const formatLabel = format === 'bibtex' ? 'BibTeX' : format === 'apa' ? 'APA' : 'Markdown';
copyButton.title = `${formatLabel} copied`;
setTimeout(() => {
copyButton.title = 'Copy citation';
}, COPY_FEEDBACK_MS);

trackEvent({ name: 'cite_copy', params: { format, smell: slug } });
});
});
Expand Down Expand Up @@ -156,19 +185,25 @@ const url = `${SITE_URL}/smells/${slug}`;
padding: 14px 44px 14px 14px;
}

.cite-block__code {
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.55;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-word;
margin: 0;
/* Override global <pre> styles: keep citation text on the white cite-block surface. */
.cite-bib-block {
background: transparent;
padding: 0;
border-radius: 0;
overflow: visible;
margin: 0;
}

.cite-block__code {
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.7;
color: var(--text-secondary);
transition: opacity 0.15s ease;
}

.cite-block__code--fading {
opacity: 0;
transition: none;
}

.cite-copy-btn {
Expand Down
11 changes: 9 additions & 2 deletions src/components/engagement/FeedbackButton.astro
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const { slug } = Astro.props;
gap: 10px;
flex: 1;
min-width: 0;
position: relative;
}

.engage-feedback__label {
Expand Down Expand Up @@ -184,10 +185,14 @@ const { slug } = Astro.props;
}

.engage-feedback__followup {
display: none;
display: inline-flex;
visibility: hidden;
opacity: 0;
position: absolute;
font-size: var(--text-sm);
font-weight: 500;
color: var(--text-secondary);
transition: opacity 0.2s ease;
}

.engage-feedback__followup a {
Expand All @@ -210,7 +215,9 @@ const { slug } = Astro.props;
}

.engage-feedback.submitted .engage-feedback__followup.active {
display: inline-flex;
visibility: visible;
opacity: 1;
position: static;
}

@media (max-width: 640px) {
Expand Down
8 changes: 6 additions & 2 deletions src/components/engagement/ShareButton.astro
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,12 @@ const shareDropdownId = `share-dropdown-${slug}`;

@media (max-width: 640px) {
.share-dropdown {
bottom: auto;
top: calc(100% + 8px);
left: 50%;
transform: translateX(-50%) translateY(4px) scale(0.96);
}

.share-dropdown--open {
transform: translateX(-50%) translateY(0) scale(1);
}
}
</style>
6 changes: 6 additions & 0 deletions src/components/islands/FilterSidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,12 @@
transition: all 0.25s var(--ease-spring);
}

.filter-sidebar__mobile-fab--hidden {
opacity: 0;
pointer-events: none;
transform: translateX(-50%) translateY(12px);
}

.filter-sidebar__mobile-fab:hover {
transform: translateX(-50%) scale(1.04);
box-shadow: var(--shadow-lg);
Expand Down
Loading
Loading