claudecode-omc 5.6.6 → 5.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/.local/skills/THIRD_PARTY_LICENSES/AvdLee-SwiftUI-Agent-Skill.LICENSE +21 -0
  2. package/.local/skills/THIRD_PARTY_LICENSES/Dimillian-Skills.LICENSE +21 -0
  3. package/.local/skills/THIRD_PARTY_LICENSES/README.md +36 -0
  4. package/.local/skills/THIRD_PARTY_LICENSES/twostraws-swiftui-agent-skill.LICENSE +21 -0
  5. package/.local/skills/h5-to-swiftui/SKILL.md +201 -0
  6. package/.local/skills/h5-to-swiftui/assets/calibration/README.md +176 -0
  7. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/index.html +52 -0
  8. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/style.css +133 -0
  9. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Package.swift +26 -0
  10. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Sources/CalibrationScreen/CalibrationScreen.swift +142 -0
  11. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Package.swift +32 -0
  12. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Sources/CalibrationScreenDivergent/CalibrationScreenDivergent.swift +122 -0
  13. package/.local/skills/h5-to-swiftui/assets/calibration/tokens.json +42 -0
  14. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/index.html +14 -0
  15. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/package.json +20 -0
  16. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/001.json +96 -0
  17. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/index.json +89 -0
  18. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.jsx +22 -0
  19. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.module.css +11 -0
  20. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.jsx +53 -0
  21. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.module.css +139 -0
  22. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.jsx +37 -0
  23. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.module.css +72 -0
  24. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.jsx +30 -0
  25. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.module.css +50 -0
  26. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.jsx +159 -0
  27. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.module.css +21 -0
  28. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/main.jsx +12 -0
  29. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.jsx +182 -0
  30. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.module.css +294 -0
  31. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.jsx +147 -0
  32. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.module.css +161 -0
  33. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/global.css +50 -0
  34. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/tokens.css +103 -0
  35. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/vite.config.js +6 -0
  36. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/data/tasks.js +67 -0
  37. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/index.html +26 -0
  38. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/router.js +73 -0
  39. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/detail.js +164 -0
  40. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/home.js +53 -0
  41. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/list.js +87 -0
  42. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/app.css +342 -0
  43. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/tokens.css +68 -0
  44. package/.local/skills/h5-to-swiftui/references/css-to-swiftui-map.md +205 -0
  45. package/.local/skills/h5-to-swiftui/references/design-token-extraction.md +209 -0
  46. package/.local/skills/h5-to-swiftui/references/high-risk-triage.md +209 -0
  47. package/.local/skills/h5-to-swiftui/references/render-equivalence-calibration.md +193 -0
  48. package/.local/skills/h5-to-swiftui/references/stack-detection.md +160 -0
  49. package/.local/skills/h5-to-swiftui/references/visual-diff-loop-protocol.md +365 -0
  50. package/.local/skills/h5-to-swiftui/scripts/_calib-consts.mjs +150 -0
  51. package/.local/skills/h5-to-swiftui/scripts/_imglib.mjs +547 -0
  52. package/.local/skills/h5-to-swiftui/scripts/_provenance.mjs +123 -0
  53. package/.local/skills/h5-to-swiftui/scripts/calibrate-render.mjs +625 -0
  54. package/.local/skills/h5-to-swiftui/scripts/capture-reference.mjs +386 -0
  55. package/.local/skills/h5-to-swiftui/scripts/detect-stack.mjs +305 -0
  56. package/.local/skills/h5-to-swiftui/scripts/evaluate-convergence.mjs +1093 -0
  57. package/.local/skills/h5-to-swiftui/scripts/extract-tokens.mjs +600 -0
  58. package/.local/skills/h5-to-swiftui/scripts/mark-overlay.mjs +379 -0
  59. package/.local/skills/h5-to-swiftui/scripts/pixel-diff.mjs +530 -0
  60. package/.local/skills/h5-to-swiftui/scripts/sim-screenshot.sh +544 -0
  61. package/.local/skills/ios-debugger-agent/SKILL.md +51 -0
  62. package/.local/skills/ios-debugger-agent/agents/openai.yaml +4 -0
  63. package/.local/skills/swift-concurrency-expert/SKILL.md +105 -0
  64. package/.local/skills/swift-concurrency-expert/agents/openai.yaml +4 -0
  65. package/.local/skills/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  66. package/.local/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  67. package/.local/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  68. package/.local/skills/swiftui-expert-skill/SKILL.md +162 -0
  69. package/.local/skills/swiftui-expert-skill/references/accessibility-patterns.md +215 -0
  70. package/.local/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  71. package/.local/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  72. package/.local/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  73. package/.local/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  74. package/.local/skills/swiftui-expert-skill/references/charts.md +602 -0
  75. package/.local/skills/swiftui-expert-skill/references/focus-patterns.md +299 -0
  76. package/.local/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  77. package/.local/skills/swiftui-expert-skill/references/latest-apis.md +488 -0
  78. package/.local/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  79. package/.local/skills/swiftui-expert-skill/references/liquid-glass.md +423 -0
  80. package/.local/skills/swiftui-expert-skill/references/list-patterns.md +446 -0
  81. package/.local/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  82. package/.local/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  83. package/.local/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  84. package/.local/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  85. package/.local/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  86. package/.local/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  87. package/.local/skills/swiftui-expert-skill/references/state-management.md +388 -0
  88. package/.local/skills/swiftui-expert-skill/references/text-patterns.md +32 -0
  89. package/.local/skills/swiftui-expert-skill/references/trace-analysis.md +295 -0
  90. package/.local/skills/swiftui-expert-skill/references/trace-recording.md +134 -0
  91. package/.local/skills/swiftui-expert-skill/references/view-structure.md +780 -0
  92. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/analyze_trace.cpython-313.pyc +0 -0
  93. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/record_trace.cpython-313.pyc +0 -0
  94. package/.local/skills/swiftui-expert-skill/scripts/analyze_trace.py +301 -0
  95. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__init__.py +1 -0
  96. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/causes.cpython-313.pyc +0 -0
  98. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/correlate.cpython-313.pyc +0 -0
  99. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/events.cpython-313.pyc +0 -0
  100. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hangs.cpython-313.pyc +0 -0
  101. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hitches.cpython-313.pyc +0 -0
  102. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/summary.cpython-313.pyc +0 -0
  103. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/swiftui.cpython-313.pyc +0 -0
  104. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/time_profiler.cpython-313.pyc +0 -0
  105. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xctrace.cpython-313.pyc +0 -0
  106. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xml_utils.cpython-313.pyc +0 -0
  107. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/causes.py +187 -0
  108. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/correlate.py +179 -0
  109. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/events.py +291 -0
  110. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hangs.py +108 -0
  111. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hitches.py +145 -0
  112. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/summary.py +243 -0
  113. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/swiftui.py +195 -0
  114. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/time_profiler.py +135 -0
  115. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +117 -0
  116. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +224 -0
  117. package/.local/skills/swiftui-expert-skill/scripts/record_trace.py +252 -0
  118. package/.local/skills/swiftui-liquid-glass/SKILL.md +90 -0
  119. package/.local/skills/swiftui-liquid-glass/agents/openai.yaml +4 -0
  120. package/.local/skills/swiftui-liquid-glass/references/liquid-glass.md +280 -0
  121. package/.local/skills/swiftui-performance-audit/SKILL.md +106 -0
  122. package/.local/skills/swiftui-performance-audit/agents/openai.yaml +4 -0
  123. package/.local/skills/swiftui-performance-audit/references/code-smells.md +150 -0
  124. package/.local/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  125. package/.local/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  126. package/.local/skills/swiftui-performance-audit/references/profiling-intake.md +44 -0
  127. package/.local/skills/swiftui-performance-audit/references/report-template.md +47 -0
  128. package/.local/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  129. package/.local/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  130. package/.local/skills/swiftui-pro/SKILL.md +108 -0
  131. package/.local/skills/swiftui-pro/agents/openai.yaml +10 -0
  132. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
  133. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
  134. package/.local/skills/swiftui-pro/references/accessibility.md +13 -0
  135. package/.local/skills/swiftui-pro/references/api.md +39 -0
  136. package/.local/skills/swiftui-pro/references/data.md +43 -0
  137. package/.local/skills/swiftui-pro/references/design.md +32 -0
  138. package/.local/skills/swiftui-pro/references/hygiene.md +9 -0
  139. package/.local/skills/swiftui-pro/references/navigation.md +14 -0
  140. package/.local/skills/swiftui-pro/references/performance.md +46 -0
  141. package/.local/skills/swiftui-pro/references/swift.md +56 -0
  142. package/.local/skills/swiftui-pro/references/views.md +36 -0
  143. package/.local/skills/swiftui-ui-patterns/SKILL.md +95 -0
  144. package/.local/skills/swiftui-ui-patterns/agents/openai.yaml +4 -0
  145. package/.local/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  146. package/.local/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  147. package/.local/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  148. package/.local/skills/swiftui-ui-patterns/references/controls.md +57 -0
  149. package/.local/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  150. package/.local/skills/swiftui-ui-patterns/references/focus.md +90 -0
  151. package/.local/skills/swiftui-ui-patterns/references/form.md +97 -0
  152. package/.local/skills/swiftui-ui-patterns/references/grids.md +71 -0
  153. package/.local/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  154. package/.local/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  155. package/.local/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  156. package/.local/skills/swiftui-ui-patterns/references/list.md +86 -0
  157. package/.local/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  158. package/.local/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  159. package/.local/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  160. package/.local/skills/swiftui-ui-patterns/references/media.md +73 -0
  161. package/.local/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  162. package/.local/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  163. package/.local/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  164. package/.local/skills/swiftui-ui-patterns/references/performance.md +62 -0
  165. package/.local/skills/swiftui-ui-patterns/references/previews.md +48 -0
  166. package/.local/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  167. package/.local/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  168. package/.local/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  169. package/.local/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  170. package/.local/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  171. package/.local/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  172. package/.local/skills/swiftui-ui-patterns/references/theming.md +71 -0
  173. package/.local/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  174. package/.local/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  175. package/.local/skills/swiftui-view-refactor/SKILL.md +202 -0
  176. package/.local/skills/swiftui-view-refactor/agents/openai.yaml +4 -0
  177. package/.local/skills/swiftui-view-refactor/references/mv-patterns.md +161 -0
  178. package/bundled/manifest.json +1 -1
  179. package/package.json +1 -1
@@ -0,0 +1,182 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useParams, useNavigate } from 'react-router-dom';
3
+ import styles from './ArticleScreen.module.css';
4
+
5
+ export default function ArticleScreen() {
6
+ const { id } = useParams();
7
+ const navigate = useNavigate();
8
+ const [article, setArticle] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+ const [error, setError] = useState(null);
11
+
12
+ useEffect(() => {
13
+ let cancelled = false;
14
+
15
+ async function loadArticle() {
16
+ setLoading(true);
17
+ setError(null);
18
+ try {
19
+ const res = await fetch(`/api/articles/${id}`);
20
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
21
+ const data = await res.json();
22
+ if (!cancelled) setArticle(data);
23
+ } catch (err) {
24
+ if (!cancelled) setError(err.message);
25
+ } finally {
26
+ if (!cancelled) setLoading(false);
27
+ }
28
+ }
29
+
30
+ loadArticle();
31
+ return () => { cancelled = true; };
32
+ }, [id]);
33
+
34
+ if (loading) {
35
+ return (
36
+ <div className={styles.stateContainer}>
37
+ <div className={styles.spinner} aria-label="Loading article" />
38
+ <p className={styles.stateText}>Loading article…</p>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ if (error) {
44
+ return (
45
+ <div className={styles.stateContainer} role="alert">
46
+ <p className={styles.errorHeading}>Could not load article</p>
47
+ <p className={styles.stateText}>{error}</p>
48
+ <button className={styles.backButton} onClick={() => navigate('/')}>
49
+ ← Back to feed
50
+ </button>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ if (!article) return null;
56
+
57
+ const {
58
+ title,
59
+ subtitle,
60
+ author,
61
+ publishedAt,
62
+ readingTime,
63
+ tag,
64
+ body,
65
+ } = article;
66
+
67
+ const formattedDate = new Date(publishedAt).toLocaleDateString('en-US', {
68
+ month: 'long',
69
+ day: 'numeric',
70
+ year: 'numeric',
71
+ });
72
+
73
+ return (
74
+ <article className={styles.article}>
75
+ {/* ── Back navigation ───────────────────────────────────── */}
76
+ <div className={styles.topBar}>
77
+ <button className={styles.backButton} onClick={() => navigate('/')}>
78
+ ← Chronicle
79
+ </button>
80
+ <span className={styles.tagChip}>{tag}</span>
81
+ </div>
82
+
83
+ {/* ── Article header ────────────────────────────────────── */}
84
+ <header className={styles.header}>
85
+ <h1 className={styles.title}>{title}</h1>
86
+ {subtitle && (
87
+ <p className={styles.subtitle}>{subtitle}</p>
88
+ )}
89
+
90
+ <div className={styles.byline}>
91
+ <div className={styles.avatarLg} aria-hidden="true">
92
+ {author.name.slice(0, 1)}
93
+ </div>
94
+ <div className={styles.bylineText}>
95
+ <span className={styles.authorName}>{author.name}</span>
96
+ <span className={styles.authorMeta}>
97
+ {author.role} · <time dateTime={publishedAt}>{formattedDate}</time> · {readingTime} min read
98
+ </span>
99
+ </div>
100
+ </div>
101
+ </header>
102
+
103
+ {/* ── Body — text-heavy, multi-typographic-level region ─── */}
104
+ {/*
105
+ * TEXT REGION NOTE (skill stage 2):
106
+ * This body region is intentionally text-heavy with multiple typographic
107
+ * levels (h2 headlines, pull-quote, body paragraphs, code block, caption).
108
+ * The skill must apply layout-box IoU + token-color ΔE for region
109
+ * comparison, NOT glyph SSIM, because CoreText metrics diverge from
110
+ * WebKit at identical px declarations (see stack-detection.md reason 4).
111
+ */}
112
+ <div className={styles.body}>
113
+ {body.map((block, idx) => (
114
+ <ArticleBlock key={idx} block={block} />
115
+ ))}
116
+ </div>
117
+
118
+ {/* ── Article footer ────────────────────────────────────── */}
119
+ <footer className={styles.footer}>
120
+ <div className={styles.footerDivider} />
121
+ <div className={styles.authorCard}>
122
+ <div className={styles.avatarLg} aria-hidden="true">
123
+ {author.name.slice(0, 1)}
124
+ </div>
125
+ <div className={styles.authorCardText}>
126
+ <span className={styles.authorCardName}>{author.name}</span>
127
+ <span className={styles.authorCardRole}>{author.role}</span>
128
+ {author.bio && (
129
+ <p className={styles.authorBio}>{author.bio}</p>
130
+ )}
131
+ </div>
132
+ </div>
133
+ </footer>
134
+ </article>
135
+ );
136
+ }
137
+
138
+ // ── Block renderer ────────────────────────────────────────────────────────────
139
+
140
+ function ArticleBlock({ block }) {
141
+ switch (block.type) {
142
+ case 'paragraph':
143
+ return <p className={styles.paragraph}>{block.text}</p>;
144
+
145
+ case 'heading':
146
+ return <h2 className={styles.heading2}>{block.text}</h2>;
147
+
148
+ case 'subheading':
149
+ return <h3 className={styles.heading3}>{block.text}</h3>;
150
+
151
+ case 'pullquote':
152
+ return (
153
+ <blockquote className={styles.pullquote}>
154
+ <p className={styles.pullquoteText}>{block.text}</p>
155
+ {block.attribution && (
156
+ <cite className={styles.pullquoteAttribution}>{block.attribution}</cite>
157
+ )}
158
+ </blockquote>
159
+ );
160
+
161
+ case 'code':
162
+ return (
163
+ <figure className={styles.codeFigure}>
164
+ {block.caption && (
165
+ <figcaption className={styles.codeCaption}>{block.caption}</figcaption>
166
+ )}
167
+ <pre className={styles.pre}>
168
+ <code className={styles.code}>{block.text}</code>
169
+ </pre>
170
+ </figure>
171
+ );
172
+
173
+ case 'caption':
174
+ return <p className={styles.caption}>{block.text}</p>;
175
+
176
+ case 'divider':
177
+ return <hr className={styles.divider} />;
178
+
179
+ default:
180
+ return null;
181
+ }
182
+ }
@@ -0,0 +1,294 @@
1
+ .article {
2
+ max-width: 720px;
3
+ margin: 0 auto;
4
+ padding: var(--space-8) var(--space-6) var(--space-12);
5
+ }
6
+
7
+ /* ── Top bar ──────────────────────────────────────────────────── */
8
+ .topBar {
9
+ display: flex;
10
+ flex-direction: row;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ margin-bottom: var(--space-8);
14
+ }
15
+
16
+ .backButton {
17
+ font-size: var(--font-size-sm);
18
+ font-weight: var(--font-weight-medium);
19
+ color: var(--color-text-accent);
20
+ padding: var(--space-2) var(--space-3);
21
+ border-radius: var(--radius-full);
22
+ border: 1px solid var(--color-border);
23
+ transition: background-color var(--duration-fast) var(--easing-default);
24
+ }
25
+
26
+ .backButton:hover {
27
+ background-color: var(--color-bg-secondary);
28
+ }
29
+
30
+ .tagChip {
31
+ font-size: var(--font-size-xs);
32
+ font-weight: var(--font-weight-semibold);
33
+ color: var(--color-text-accent);
34
+ text-transform: uppercase;
35
+ letter-spacing: 0.6px;
36
+ padding: var(--space-1) var(--space-3);
37
+ background-color: var(--color-bg-tag);
38
+ border-radius: var(--radius-full);
39
+ }
40
+
41
+ /* ── Header ───────────────────────────────────────────────────── */
42
+ .header {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: var(--space-5);
46
+ margin-bottom: var(--space-10);
47
+ padding-bottom: var(--space-8);
48
+ border-bottom: 1px solid var(--color-divider);
49
+ }
50
+
51
+ .title {
52
+ font-family: var(--font-family-display);
53
+ font-size: var(--font-size-display);
54
+ font-weight: var(--font-weight-heavy);
55
+ color: var(--color-text-primary);
56
+ line-height: var(--line-height-tight);
57
+ letter-spacing: -1.5px;
58
+ }
59
+
60
+ .subtitle {
61
+ font-size: var(--font-size-subhead);
62
+ font-weight: var(--font-weight-regular);
63
+ color: var(--color-text-secondary);
64
+ line-height: var(--line-height-relaxed);
65
+ }
66
+
67
+ .byline {
68
+ display: flex;
69
+ flex-direction: row;
70
+ align-items: center;
71
+ gap: var(--space-3);
72
+ }
73
+
74
+ .avatarLg {
75
+ width: 44px;
76
+ height: 44px;
77
+ border-radius: var(--radius-full);
78
+ background-color: var(--color-accent);
79
+ color: var(--color-text-on-accent);
80
+ font-size: var(--font-size-body-lg);
81
+ font-weight: var(--font-weight-bold);
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ flex-shrink: 0;
86
+ }
87
+
88
+ .bylineText {
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 2px;
92
+ }
93
+
94
+ .authorName {
95
+ font-size: var(--font-size-body);
96
+ font-weight: var(--font-weight-semibold);
97
+ color: var(--color-text-primary);
98
+ }
99
+
100
+ .authorMeta {
101
+ font-size: var(--font-size-sm);
102
+ color: var(--color-text-caption);
103
+ }
104
+
105
+ /* ── Body ─────────────────────────────────────────────────────── */
106
+ .body {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: var(--space-5);
110
+ }
111
+
112
+ /* Paragraph — main body text */
113
+ .paragraph {
114
+ font-size: var(--font-size-body-lg);
115
+ font-weight: var(--font-weight-regular);
116
+ color: var(--color-text-primary);
117
+ line-height: var(--line-height-relaxed);
118
+ }
119
+
120
+ /* Section heading */
121
+ .heading2 {
122
+ font-family: var(--font-family-display);
123
+ font-size: var(--font-size-title);
124
+ font-weight: var(--font-weight-bold);
125
+ color: var(--color-text-primary);
126
+ line-height: var(--line-height-tight);
127
+ letter-spacing: -0.8px;
128
+ margin-top: var(--space-4);
129
+ }
130
+
131
+ /* Sub-section heading */
132
+ .heading3 {
133
+ font-family: var(--font-family-display);
134
+ font-size: var(--font-size-headline);
135
+ font-weight: var(--font-weight-semibold);
136
+ color: var(--color-text-primary);
137
+ line-height: var(--line-height-tight);
138
+ letter-spacing: -0.4px;
139
+ margin-top: var(--space-2);
140
+ }
141
+
142
+ /* Pull-quote */
143
+ .pullquote {
144
+ border-left: 3px solid var(--color-accent);
145
+ padding: var(--space-4) var(--space-6);
146
+ background-color: var(--color-bg-secondary);
147
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: var(--space-3);
151
+ }
152
+
153
+ .pullquoteText {
154
+ font-family: var(--font-family-display);
155
+ font-size: var(--font-size-subhead);
156
+ font-weight: var(--font-weight-medium);
157
+ color: var(--color-text-primary);
158
+ line-height: var(--line-height-relaxed);
159
+ font-style: italic;
160
+ }
161
+
162
+ .pullquoteAttribution {
163
+ font-size: var(--font-size-sm);
164
+ font-weight: var(--font-weight-semibold);
165
+ color: var(--color-text-secondary);
166
+ font-style: normal;
167
+ }
168
+
169
+ /* Code block */
170
+ .codeFigure {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: var(--space-2);
174
+ }
175
+
176
+ .codeCaption {
177
+ font-size: var(--font-size-xs);
178
+ font-weight: var(--font-weight-semibold);
179
+ color: var(--color-text-secondary);
180
+ text-transform: uppercase;
181
+ letter-spacing: 0.5px;
182
+ }
183
+
184
+ .pre {
185
+ background-color: var(--color-bg-secondary);
186
+ border: 1px solid var(--color-border);
187
+ border-radius: var(--radius-md);
188
+ padding: var(--space-5);
189
+ overflow-x: auto;
190
+ }
191
+
192
+ .code {
193
+ font-family: var(--font-family-mono);
194
+ font-size: var(--font-size-sm);
195
+ color: var(--color-text-primary);
196
+ line-height: var(--line-height-relaxed);
197
+ white-space: pre;
198
+ }
199
+
200
+ /* Caption / footnote */
201
+ .caption {
202
+ font-size: var(--font-size-sm);
203
+ color: var(--color-text-caption);
204
+ line-height: var(--line-height-normal);
205
+ text-align: center;
206
+ }
207
+
208
+ /* Divider */
209
+ .divider {
210
+ border: none;
211
+ border-top: 1px solid var(--color-divider);
212
+ margin: var(--space-2) 0;
213
+ }
214
+
215
+ /* ── Footer / author card ─────────────────────────────────────── */
216
+ .footer {
217
+ margin-top: var(--space-12);
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: var(--space-6);
221
+ }
222
+
223
+ .footerDivider {
224
+ height: 1px;
225
+ background-color: var(--color-divider);
226
+ }
227
+
228
+ .authorCard {
229
+ display: flex;
230
+ flex-direction: row;
231
+ gap: var(--space-4);
232
+ align-items: flex-start;
233
+ padding: var(--space-5);
234
+ background-color: var(--color-bg-secondary);
235
+ border-radius: var(--radius-lg);
236
+ }
237
+
238
+ .authorCardText {
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: var(--space-1);
242
+ }
243
+
244
+ .authorCardName {
245
+ font-size: var(--font-size-body);
246
+ font-weight: var(--font-weight-bold);
247
+ color: var(--color-text-primary);
248
+ }
249
+
250
+ .authorCardRole {
251
+ font-size: var(--font-size-sm);
252
+ color: var(--color-text-secondary);
253
+ }
254
+
255
+ .authorBio {
256
+ font-size: var(--font-size-body);
257
+ color: var(--color-text-secondary);
258
+ line-height: var(--line-height-normal);
259
+ margin-top: var(--space-2);
260
+ }
261
+
262
+ /* ── Loading / error states ───────────────────────────────────── */
263
+ .stateContainer {
264
+ display: flex;
265
+ flex-direction: column;
266
+ align-items: center;
267
+ gap: var(--space-3);
268
+ padding: var(--space-12) var(--space-6);
269
+ }
270
+
271
+ .spinner {
272
+ width: 32px;
273
+ height: 32px;
274
+ border: 3px solid var(--color-border);
275
+ border-top-color: var(--color-accent);
276
+ border-radius: var(--radius-full);
277
+ animation: spin 0.8s linear infinite;
278
+ }
279
+
280
+ @keyframes spin {
281
+ to { transform: rotate(360deg); }
282
+ }
283
+
284
+ .stateText {
285
+ font-size: var(--font-size-body);
286
+ color: var(--color-text-secondary);
287
+ text-align: center;
288
+ }
289
+
290
+ .errorHeading {
291
+ font-size: var(--font-size-headline);
292
+ font-weight: var(--font-weight-semibold);
293
+ color: var(--color-danger);
294
+ }
@@ -0,0 +1,147 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import ArticleCard from '../components/ArticleCard.jsx';
3
+ import TagCloud from '../components/TagCloud.jsx';
4
+ import TrendChart from '../components/TrendChart.jsx';
5
+ import styles from './FeedScreen.module.css';
6
+
7
+ const ALL_TAGS = [
8
+ 'Technology',
9
+ 'Design',
10
+ 'Engineering',
11
+ 'Swift',
12
+ 'SwiftUI',
13
+ 'Machine Learning',
14
+ 'Web Performance',
15
+ 'Accessibility',
16
+ 'Open Source',
17
+ 'Developer Experience',
18
+ 'iOS 18',
19
+ 'WWDC',
20
+ ];
21
+
22
+ const TREND_DATA = [12, 19, 8, 24, 17, 31, 28, 35, 22, 40, 38, 45];
23
+
24
+ export default function FeedScreen() {
25
+ const [articles, setArticles] = useState([]);
26
+ const [loading, setLoading] = useState(true);
27
+ const [error, setError] = useState(null);
28
+ const [selectedTag, setSelectedTag] = useState(null);
29
+
30
+ useEffect(() => {
31
+ let cancelled = false;
32
+
33
+ async function loadFeed() {
34
+ setLoading(true);
35
+ setError(null);
36
+ try {
37
+ // Fetch from mock API endpoint (relative URL; real server returns JSON)
38
+ const res = await fetch('/api/articles');
39
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
40
+ const data = await res.json();
41
+ if (!cancelled) setArticles(data.articles ?? []);
42
+ } catch (err) {
43
+ if (!cancelled) setError(err.message);
44
+ } finally {
45
+ if (!cancelled) setLoading(false);
46
+ }
47
+ }
48
+
49
+ loadFeed();
50
+ return () => { cancelled = true; };
51
+ }, []);
52
+
53
+ const filtered = selectedTag
54
+ ? articles.filter(a => a.tag === selectedTag)
55
+ : articles;
56
+
57
+ return (
58
+ <div className={styles.screen}>
59
+ {/* ── Hero header ───────────────────────────────────────── */}
60
+ <section className={styles.hero}>
61
+ <div className={styles.heroInner}>
62
+ <p className={styles.heroEyebrow}>Daily Edition — May 2026</p>
63
+ <h1 className={styles.heroTitle}>The Chronicle</h1>
64
+ <p className={styles.heroSubtitle}>
65
+ Thoughtful writing on technology, design, and the craft of building software.
66
+ </p>
67
+ </div>
68
+ </section>
69
+
70
+ <div className={styles.content}>
71
+ {/* ── Trend chart — TIER3 surface ───────────────────── */}
72
+ {/* TIER3: WebGL canvas chart via TrendChart component.
73
+ This component uses WebGLRenderingContext (GLSL shaders) for
74
+ GPU-accelerated sparkline rendering. It cannot be transpiled to
75
+ SwiftUI; must be replaced with Swift Charts LineChart or WKWebView.
76
+ See TrendChart.jsx for full TIER3 annotation. */}
77
+ <section className={styles.trendSection}>
78
+ <TrendChart
79
+ data={TREND_DATA}
80
+ label="Reader engagement — last 12 weeks"
81
+ color="var(--color-accent)"
82
+ />
83
+ </section>
84
+
85
+ {/* ── Tag filter ────────────────────────────────────── */}
86
+ <section className={styles.tagsSection}>
87
+ <h2 className={styles.sectionHeading}>Topics</h2>
88
+ {/* CUSTOM-LAYOUT: flex-wrap:wrap (see TagCloud.module.css) */}
89
+ <TagCloud
90
+ tags={ALL_TAGS}
91
+ selectedTag={selectedTag}
92
+ onTagClick={setSelectedTag}
93
+ />
94
+ </section>
95
+
96
+ {/* ── Articles grid ─────────────────────────────────── */}
97
+ <section className={styles.feedSection}>
98
+ <div className={styles.feedHeader}>
99
+ <h2 className={styles.sectionHeading}>
100
+ {selectedTag ? `#${selectedTag}` : 'Latest'}
101
+ </h2>
102
+ {selectedTag && (
103
+ <button
104
+ className={styles.clearFilter}
105
+ onClick={() => setSelectedTag(null)}
106
+ >
107
+ Clear filter
108
+ </button>
109
+ )}
110
+ </div>
111
+
112
+ {loading && (
113
+ <div className={styles.stateContainer}>
114
+ <div className={styles.spinner} aria-label="Loading articles" />
115
+ <p className={styles.stateText}>Loading articles…</p>
116
+ </div>
117
+ )}
118
+
119
+ {error && !loading && (
120
+ <div className={styles.stateContainer} role="alert">
121
+ <p className={styles.errorHeading}>Failed to load feed</p>
122
+ <p className={styles.stateText}>{error}</p>
123
+ </div>
124
+ )}
125
+
126
+ {!loading && !error && filtered.length === 0 && (
127
+ <div className={styles.stateContainer}>
128
+ <p className={styles.stateText}>
129
+ {selectedTag
130
+ ? `No articles tagged "${selectedTag}" yet.`
131
+ : 'No articles found.'}
132
+ </p>
133
+ </div>
134
+ )}
135
+
136
+ {!loading && !error && filtered.length > 0 && (
137
+ <div className={styles.grid}>
138
+ {filtered.map(article => (
139
+ <ArticleCard key={article.id} article={article} />
140
+ ))}
141
+ </div>
142
+ )}
143
+ </section>
144
+ </div>
145
+ </div>
146
+ );
147
+ }