목적
corp-web-app의 Site Notice / Floating Spotlight Card 구현을 기준으로, QueryPie Docs의 오른쪽 Sidebar 영역에 Spotlight 광고판을 추가한다.
현재 요청의 원문 범위는 “홈페이지의 Spotlight 광고판을 오른쪽 Sidebar에 동일하게 노출하기”다. 구현 시에는 corp-web-app의 floating card UX와 visibility/analytics 계약을 참고하되, querypie-docs의 Nextra right TOC/sidebar 구조에 맞게 sidebar-embedded card로 적용한다.
참조
- 기준 이슈: querypie/corp-web-app#1044
corp-web-app 확인 기준: origin/main 7320add2f248948d5e6d69d9f909806e44f57d55
- 주요 참조 구현:
src/components/sections/floating-spotlight-card/floating-spotlight-card.component.tsx
src/components/sections/floating-spotlight-card/floating-spotlight-card-position.ts
src/components/sections/floating-spotlight-card/floating-spotlight-card.module.css
src/lib/resources/site-notice.ts
src/lib/resources/site-notice-spotlight-storage.ts
src/utils/site-notice-utm.ts
src/utils/site-notice-analytics.ts
querypie-docs의 예상 integration point:
src/app/[lang]/layout.tsx의 Nextra Layout toc.extraContent
- 기존
ConfluenceSourceLink는 유지하고, Spotlight Card를 같은 right sidebar 영역에 함께 배치한다.
범위
In scope
- 오른쪽 Sidebar / TOC 영역에 Spotlight Card 추가
- locale별 Spotlight 광고판 데이터 모델과 active item filtering
- 데스크톱 노출, 모바일 숨김 처리
- carousel rotation, 이전/다음 controls, indicator, hover/focus pause
- CTA click 및 dismiss 동작
- browser-local 30일 visibility suppression
- owned URL UTM 부여 및 GA event 전송
- 관련 unit/component/integration 수준 검증
Out of scope
corp-web-app 코드 변경
querypie-docs의 left navigation sidebar 구조 변경
- 문서 본문 MDX 구조 변경
- sitemap, canonical, redirect, Nextra pageMap 변경
- 모바일 Top Announcement Bar 추가
- local dev server 기반 시각 검증을 필수 완료 조건으로 삼는 것
기능 명세
1. 배치
- Spotlight Card는 Nextra 오른쪽 TOC/sidebar 영역에 렌더링한다.
src/app/[lang]/layout.tsx의 toc.extraContent를 확장해 기존 ConfluenceSourceLink와 함께 노출한다.
- 카드가 렌더되지 않는 상태에서도 right sidebar와 문서 본문 레이아웃에 빈 placeholder를 남기지 않는다.
- 카드 추가로
On This Page, 언어 선택기, Confluence source link의 접근성과 클릭 영역이 깨지면 안 된다.
- 오른쪽 Sidebar가 없는 viewport 또는 Nextra가 TOC/sidebar를 숨기는 viewport에서는 카드도 숨긴다.
2. 데이터 모델
Spotlight 데이터는 locale별로 관리한다. 구현 위치는 PR에서 확정하되, 다음 field를 표현할 수 있어야 한다.
type DocsSpotlightContent = {
ariaLabel: string;
spotlightLabel: string;
spotlightCtaLabel: string;
spotlightDismissLabel: string;
nextLabel: string;
previousLabel: string;
items: DocsSpotlightItem[];
};
type DocsSpotlightItem = {
id: string;
date: string; // YYYY-MM-DD
href: string;
imageSrc: string;
imageAlt: string;
title: string;
spotlightMeta: string;
visibleUntil: string; // YYYY-MM-DD, inclusive
};
id는 campaign을 식별하는 구체적인 kebab-case semantic key를 사용한다.
date와 visibleUntil은 YYYY-MM-DD 형식이어야 한다.
visibleUntil은 inclusive active filter로 동작한다.
- active item이 없으면 Spotlight Card를 렌더하지 않는다.
- 같은 campaign은 locale별로 같은
id를 사용한다.
- 특정 locale에만 노출해야 하는 campaign은 허용하되, 데이터 검증에서 의도적인 locale-specific item임을 식별할 수 있어야 한다.
href는 canonical destination만 저장하고, UTM은 render 시점에 부여한다.
imageSrc는 repo-local public asset path를 우선 사용한다.
3. Active item ordering
- active item은
visibleUntil >= today인 item이다.
- 날짜 기준은 Seoul business day 기준으로 계산한다.
- 여러 active item이 있으면 최신
date item을 우선 노출한다.
- 나머지 item은 재방문 피로도를 낮추기 위해 shuffle하거나, 테스트 가능한 deterministic random injection을 제공한다.
- 모든 active item이 visibility suppression 대상이면 카드 전체를 렌더하지 않는다.
4. Card UI
- card는 오른쪽 Sidebar 폭 안에 맞는 compact card로 렌더링한다.
corp-web-app Floating Spotlight Card와 동일한 정보 구조를 따른다.
- eyebrow / label
- dismiss button
- image
- title
- meta/date
- CTA
- indicator
- previous / next controls
- title은 최대 2줄로 clamp하고, 긴 텍스트가 sidebar 밖으로 overflow되면 안 된다.
- image는 고정된 frame 안에서
object-fit: cover로 표시한다.
- CTA는 card link와 동일 목적지를 가리키며, keyboard focus 상태가 명확해야 한다.
- dismiss button은 accessible name을 가져야 한다.
- previous / next buttons는 locale별
previousLabel, nextLabel을 accessible name으로 사용한다.
- indicator는 현재 active item을 시각적으로 표시한다.
5. Responsive behavior
- card는 desktop/right-sidebar viewport에서만 보인다.
corp-web-app 기준과 동일하게 max-width: 1023px 이하에서는 숨기는 것을 기본값으로 한다.
- 모바일에서는 별도 Top Announcement Bar를 만들지 않는다.
- browser-local visibility state 확인 전에는 initial flash를 만들지 않도록 card body를 렌더하지 않는다.
6. Carousel behavior
- item이 2개 이상이면 4초 interval로 다음 item으로 rotation한다.
- hover 또는 keyboard focus가 card 내부에 있으면 rotation을 pause한다.
- focus가 card 밖으로 이동하거나 hover가 끝나면 rotation을 resume한다.
prefers-reduced-motion: reduce 환경에서는 auto rotation을 실행하지 않는다.
- previous / next controls는 suppression 이후 남은 renderable item 기준으로 순환한다.
- view event는 같은 page lifecycle에서 item별 1회만 전송한다.
7. Visibility suppression
corp-web-app의 Spotlight visibility gate를 docs용으로 가져온다.
- browser
localStorage에 versioned namespace key를 사용한다.
- 권장 key:
querypie:docs:spotlight:v1
- storage value는 item
id를 key로 하는 object map이다.
type DocsSpotlightVisibilityRecord = {
disposition: 'viewed' | 'dismissed';
updatedAt: string; // ISO timestamp
expiresAt: string; // ISO timestamp
};
- CTA click은 active item에
disposition: 'viewed' record를 저장한다.
- dismiss click은 active item에
disposition: 'dismissed' record를 저장하고 현재 card instance를 숨긴다.
- record TTL은 저장 시점 기준 30일이다.
- unexpired
viewed 또는 dismissed record가 있는 item은 card render/rotation 대상에서 제외한다.
- expired record는 suppression하지 않고, read 또는 write 시점에 prune할 수 있다.
- localStorage read, parse, schema validation, write 실패는 recoverable하게 처리한다.
- storage 실패가 CTA navigation, dismiss UI state, page rendering을 막으면 안 된다.
8. URL tracking
- owned QueryPie URL에만 UTM을 append한다.
- external URL은 원본 href를 유지한다.
- relative URL은 docs/current locale route semantics를 유지해야 한다.
- 권장 UTM taxonomy:
utm_source=qp
utm_medium=notice
utm_campaign=<spotlight-item-id>
utm_content=docs_sidebar_card
utm_id=sn_<spotlight-item-id>
- 기존 query string과 hash가 있으면 보존한다.
9. Analytics
production에서 gtag가 있으면 corp-web-app과 호환되는 promotion event 계열을 전송한다.
- view:
view_promotion
- click:
select_promotion
- dismiss:
site_notice_dismiss
공통 parameter는 다음 값을 포함한다.
promotion_id: sn_<spotlight-item-id>
promotion_name: <spotlight-item-id>
creative_slot: docs_sidebar_card
creative_name: <title>
spotlight_id: <spotlight-item-id>
spotlight_surface: docs_sidebar_card
spotlight_title: <title>
spotlight_destination: <canonical href>
Analytics failure는 navigation, dismiss, visibility persistence, rendering을 막으면 안 된다.
10. 접근성
- Card container는
aside 또는 equivalent landmark semantics를 사용하고 locale별 ariaLabel을 제공한다.
- Carousel active content 변경은 과도한 screen reader announcement를 만들지 않아야 한다.
- 비활성 carousel item이 DOM에 남는 구현이라면 keyboard tab 순서에서 제외한다.
- Dismiss, previous, next controls는 keyboard로 조작 가능해야 한다.
- Focus-visible outline을 제공한다.
Acceptance criteria
- 오른쪽 Sidebar/TOC가 보이는 desktop viewport에서 Spotlight Card가 노출된다.
- 오른쪽 Sidebar/TOC가 숨겨지는 viewport에서는 Spotlight Card도 숨겨진다.
- active item이 없거나 모든 active item이 unexpired visibility record로 suppressed되면 아무것도 렌더하지 않는다.
- CTA click은 UTM이 붙은 destination으로 이동하고, 해당 item의
viewed record를 30일 TTL로 저장한다.
- dismiss click은
dismissed record를 30일 TTL로 저장하고 현재 card를 숨긴다.
- localStorage가 unavailable 또는 corrupt 상태여도 page rendering은 실패하지 않는다.
- hover/focus pause, previous/next controls, reduced-motion 조건이 동작한다.
- existing
ConfluenceSourceLink, language selector, TOC title 영역은 회귀하지 않는다.
- GA event가 가능한 환경에서는 view/click/dismiss event가 전송되고, 불가능한 환경에서는 조용히 no-op 처리된다.
검증 계획
- Data loader/schema unit test
- 필수 field, date format, image path, active filtering, inclusive
visibleUntil, no active item case
- Visibility storage unit test
- read/write, TTL, expired pruning, corrupt payload recovery, storage exception recovery
- URL tracking unit test
- owned URL UTM append, external URL no-op, existing query/hash 보존
- Component test
- desktop render, mobile hidden, initial flash 방지, carousel rotation, hover/focus pause, reduced-motion no auto rotation, previous/next, CTA click, dismiss
- Layout integration test 또는 source-level assertion
toc.extraContent에 Spotlight Card와 기존 ConfluenceSourceLink가 함께 남아 있는지 확인
- 문서/issue 명세만 갱신하는 현재 작업은 local build 대상이 아니며, 구현 PR에서 위 검증을 수행한다.
목적
corp-web-app의 Site Notice / Floating Spotlight Card 구현을 기준으로, QueryPie Docs의 오른쪽 Sidebar 영역에 Spotlight 광고판을 추가한다.현재 요청의 원문 범위는 “홈페이지의 Spotlight 광고판을 오른쪽 Sidebar에 동일하게 노출하기”다. 구현 시에는
corp-web-app의 floating card UX와 visibility/analytics 계약을 참고하되,querypie-docs의 Nextra right TOC/sidebar 구조에 맞게 sidebar-embedded card로 적용한다.참조
corp-web-app확인 기준:origin/main7320add2f248948d5e6d69d9f909806e44f57d55src/components/sections/floating-spotlight-card/floating-spotlight-card.component.tsxsrc/components/sections/floating-spotlight-card/floating-spotlight-card-position.tssrc/components/sections/floating-spotlight-card/floating-spotlight-card.module.csssrc/lib/resources/site-notice.tssrc/lib/resources/site-notice-spotlight-storage.tssrc/utils/site-notice-utm.tssrc/utils/site-notice-analytics.tsquerypie-docs의 예상 integration point:src/app/[lang]/layout.tsx의 NextraLayouttoc.extraContentConfluenceSourceLink는 유지하고, Spotlight Card를 같은 right sidebar 영역에 함께 배치한다.범위
In scope
Out of scope
corp-web-app코드 변경querypie-docs의 left navigation sidebar 구조 변경기능 명세
1. 배치
src/app/[lang]/layout.tsx의toc.extraContent를 확장해 기존ConfluenceSourceLink와 함께 노출한다.On This Page, 언어 선택기, Confluence source link의 접근성과 클릭 영역이 깨지면 안 된다.2. 데이터 모델
Spotlight 데이터는 locale별로 관리한다. 구현 위치는 PR에서 확정하되, 다음 field를 표현할 수 있어야 한다.
id는 campaign을 식별하는 구체적인 kebab-case semantic key를 사용한다.date와visibleUntil은YYYY-MM-DD형식이어야 한다.visibleUntil은 inclusive active filter로 동작한다.id를 사용한다.href는 canonical destination만 저장하고, UTM은 render 시점에 부여한다.imageSrc는 repo-local public asset path를 우선 사용한다.3. Active item ordering
visibleUntil >= today인 item이다.dateitem을 우선 노출한다.4. Card UI
corp-web-appFloating Spotlight Card와 동일한 정보 구조를 따른다.object-fit: cover로 표시한다.previousLabel,nextLabel을 accessible name으로 사용한다.5. Responsive behavior
corp-web-app기준과 동일하게max-width: 1023px이하에서는 숨기는 것을 기본값으로 한다.6. Carousel behavior
prefers-reduced-motion: reduce환경에서는 auto rotation을 실행하지 않는다.7. Visibility suppression
corp-web-app의 Spotlight visibility gate를 docs용으로 가져온다.localStorage에 versioned namespace key를 사용한다.querypie:docs:spotlight:v1id를 key로 하는 object map이다.disposition: 'viewed'record를 저장한다.disposition: 'dismissed'record를 저장하고 현재 card instance를 숨긴다.viewed또는dismissedrecord가 있는 item은 card render/rotation 대상에서 제외한다.8. URL tracking
utm_source=qputm_medium=noticeutm_campaign=<spotlight-item-id>utm_content=docs_sidebar_cardutm_id=sn_<spotlight-item-id>9. Analytics
production에서
gtag가 있으면corp-web-app과 호환되는 promotion event 계열을 전송한다.view_promotionselect_promotionsite_notice_dismiss공통 parameter는 다음 값을 포함한다.
promotion_id: sn_<spotlight-item-id>promotion_name: <spotlight-item-id>creative_slot: docs_sidebar_cardcreative_name: <title>spotlight_id: <spotlight-item-id>spotlight_surface: docs_sidebar_cardspotlight_title: <title>spotlight_destination: <canonical href>Analytics failure는 navigation, dismiss, visibility persistence, rendering을 막으면 안 된다.
10. 접근성
aside또는 equivalent landmark semantics를 사용하고 locale별ariaLabel을 제공한다.Acceptance criteria
viewedrecord를 30일 TTL로 저장한다.dismissedrecord를 30일 TTL로 저장하고 현재 card를 숨긴다.ConfluenceSourceLink, language selector, TOC title 영역은 회귀하지 않는다.검증 계획
visibleUntil, no active item casetoc.extraContent에 Spotlight Card와 기존ConfluenceSourceLink가 함께 남아 있는지 확인