Skip to content

Commit 92d3a0a

Browse files
committed
documentation notes, feedback and npm caching
1 parent e887c35 commit 92d3a0a

65 files changed

Lines changed: 5979 additions & 479 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,25 @@ Since `listRoles` is wrapped in `createServerFn`, TanStack Start will properly h
161161
**The `dev` command does not end - it runs indefinitely in watch mode.**
162162

163163
When agents need to test build output or verify that the project builds successfully, use the `build` command instead of `dev`. The `build` command will complete and exit, making it suitable for automated testing and verification.
164+
165+
### Testing Limitations
166+
167+
**Agents cannot run end-to-end tests without a headless browser.**
168+
169+
This is a TanStack Start application that requires a browser environment to fully test. Agents can:
170+
171+
- ✅ Run TypeScript compilation (`pnpm tsc --noEmit`) to check for type errors
172+
- ✅ Run the build command (`pnpm build`) to verify the project builds successfully
173+
- ✅ Inspect build output and generated files
174+
175+
Agents cannot:
176+
177+
- ❌ Start the dev server and interact with the application (no headless browser)
178+
- ❌ Test UI functionality or user interactions
179+
- ❌ Verify runtime behavior in the browser
180+
- ❌ Test API endpoints that require browser context
181+
182+
For runtime testing and verification, developers should:
183+
1. Review the code changes
184+
2. Start the dev server manually (`pnpm dev`)
185+
3. Test the functionality in a browser
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import * as React from 'react'
2+
import { twMerge } from 'tailwind-merge'
3+
import { DocFeedbackFloatingButton } from './DocFeedbackFloatingButton'
4+
import { DocFeedbackNote } from './DocFeedbackNote'
5+
import { useDocFeedbackRequired } from './DocFeedbackContext'
6+
import { getBlockIdentifier } from '~/utils/docFeedback.client'
7+
8+
interface BlockWithFeedbackProps {
9+
children: React.ReactNode
10+
className?: string
11+
}
12+
13+
export function BlockWithFeedback({
14+
children,
15+
className,
16+
}: BlockWithFeedbackProps) {
17+
const blockRef = React.useRef<HTMLDivElement>(null)
18+
const [isHovered, setIsHovered] = React.useState(false)
19+
const [blockSelector, setBlockSelector] = React.useState<string | null>(null)
20+
const [contentHash, setContentHash] = React.useState<string | undefined>()
21+
22+
const feedback = useDocFeedbackRequired()
23+
24+
// Generate block selector on mount
25+
React.useEffect(() => {
26+
if (blockRef.current) {
27+
// Get the first child element (the actual content block, not our wrapper)
28+
const contentElement = blockRef.current.firstElementChild as HTMLElement
29+
if (contentElement) {
30+
getBlockIdentifier(contentElement).then((identifier) => {
31+
setBlockSelector(identifier.selector)
32+
setContentHash(identifier.contentHash)
33+
})
34+
}
35+
}
36+
}, [])
37+
38+
// Find note for this block
39+
const note = React.useMemo(() => {
40+
if (!blockSelector) return null
41+
return feedback.userNotes.find((n) => n.blockSelector === blockSelector)
42+
}, [feedback.userNotes, blockSelector])
43+
44+
const isCollapsed = note ? feedback.collapsedNotes.has(note.id) : false
45+
const hasNote = !!note
46+
47+
const handleAddNote = React.useCallback(() => {
48+
if (blockSelector) {
49+
feedback.onAddFeedback(blockSelector, contentHash, 'note')
50+
}
51+
}, [blockSelector, contentHash, feedback])
52+
53+
const handleAddFeedback = React.useCallback(() => {
54+
if (blockSelector) {
55+
feedback.onAddFeedback(blockSelector, contentHash, 'improvement')
56+
}
57+
}, [blockSelector, contentHash, feedback])
58+
59+
const handleShowNote = React.useCallback(() => {
60+
if (note) {
61+
feedback.onShowNote(note.id)
62+
}
63+
}, [note, feedback])
64+
65+
const handleMouseEnter = React.useCallback(() => {
66+
setIsHovered(true)
67+
}, [])
68+
69+
const handleMouseLeave = React.useCallback(() => {
70+
setIsHovered(false)
71+
}, [])
72+
73+
return (
74+
<>
75+
<div
76+
ref={blockRef}
77+
className={twMerge(
78+
'relative doc-feedback-block',
79+
isHovered && 'doc-feedback-block-highlighted',
80+
className,
81+
)}
82+
onMouseEnter={handleMouseEnter}
83+
onMouseLeave={handleMouseLeave}
84+
data-feedback-wrapper="true"
85+
style={{
86+
anchorName: blockSelector ? `--feedback-${blockSelector.replace(/[^a-zA-Z0-9]/g, '-')}` : undefined,
87+
}}
88+
>
89+
{children}
90+
91+
{/* Floating button */}
92+
{blockSelector && (
93+
<div
94+
className={twMerge(
95+
'absolute top-0 right-0 -translate-y-full z-50 transition-opacity duration-200',
96+
hasNote || isHovered ? 'opacity-100' : 'opacity-0',
97+
)}
98+
>
99+
<DocFeedbackFloatingButton
100+
onAddNote={handleAddNote}
101+
onAddFeedback={handleAddFeedback}
102+
onMouseEnter={handleMouseEnter}
103+
onMouseLeave={handleMouseLeave}
104+
hasNote={hasNote}
105+
onShowNote={handleShowNote}
106+
/>
107+
</div>
108+
)}
109+
</div>
110+
111+
{/* Note display - inline in document flow */}
112+
{note && blockSelector && (
113+
<div className="my-4">
114+
<DocFeedbackNote
115+
note={note}
116+
anchorName=""
117+
inline={true}
118+
/>
119+
</div>
120+
)}
121+
</>
122+
)
123+
}

src/components/ClientAuth.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ErrorComponent } from '@tanstack/react-router'
22
import React from 'react'
3-
import { FaSpinner } from 'react-icons/fa'
3+
import { Spinner } from '~/components/Spinner'
44
import { SignInForm } from '~/routes/_libraries/login'
55
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
66

@@ -18,7 +18,7 @@ export function ClientAuth({
1818
if (userQuery.isLoading) {
1919
return (
2020
<div className={baseClasses}>
21-
<FaSpinner className="animate-spin" />
21+
<Spinner />
2222
</div>
2323
)
2424
}
@@ -49,7 +49,7 @@ export function ClientAdminAuth({ children }: { children: React.ReactNode }) {
4949
if (userQuery.isLoading) {
5050
return (
5151
<div className={baseClasses}>
52-
<FaSpinner className="animate-spin" />
52+
<Spinner />
5353
</div>
5454
)
5555
}

src/components/Doc.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CopyMarkdownButton } from './CopyMarkdownButton'
1717
import { GamHeader } from './Gam'
1818
import { Toc } from './Toc'
1919
import { TocMobile } from './TocMobile'
20+
import { DocFeedbackProvider } from './DocFeedbackProvider'
2021

2122
type DocProps = {
2223
title: string
@@ -27,6 +28,10 @@ type DocProps = {
2728
shouldRenderToc?: boolean
2829
colorFrom?: string
2930
colorTo?: string
31+
// Feedback props (optional)
32+
libraryId?: string
33+
libraryVersion?: string
34+
pagePath?: string
3035
}
3136

3237
function DocContent({
@@ -38,6 +43,9 @@ function DocContent({
3843
shouldRenderToc = false,
3944
colorFrom,
4045
colorTo,
46+
libraryId,
47+
libraryVersion,
48+
pagePath,
4149
}: DocProps) {
4250
const { headings } = useMarkdownHeadings()
4351

@@ -159,7 +167,17 @@ function DocContent({
159167
'styled-markdown-content',
160168
)}
161169
>
162-
<Markdown rawContent={content} />
170+
{libraryId && libraryVersion && pagePath ? (
171+
<DocFeedbackProvider
172+
pagePath={pagePath}
173+
libraryId={libraryId}
174+
libraryVersion={libraryVersion}
175+
>
176+
<Markdown rawContent={content} />
177+
</DocFeedbackProvider>
178+
) : (
179+
<Markdown rawContent={content} />
180+
)}
163181
</div>
164182
<div className="h-12" />
165183
<div className="w-full h-px bg-gray-500 opacity-30" />
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as React from 'react'
2+
import type { DocFeedback } from '~/db/schema'
3+
4+
interface DocFeedbackContextValue {
5+
// User feedback for the current page
6+
userNotes: DocFeedback[]
7+
8+
// Collapsed state
9+
collapsedNotes: Set<string>
10+
toggleNote: (noteId: string) => void
11+
12+
// Actions
13+
onAddFeedback: (blockSelector: string, blockContentHash: string | undefined, type: 'note' | 'improvement') => void
14+
onEditNote: (note: DocFeedback) => void
15+
onShowNote: (noteId: string) => void
16+
17+
// Page info
18+
pagePath: string
19+
libraryId: string
20+
libraryVersion: string
21+
}
22+
23+
const DocFeedbackContext = React.createContext<DocFeedbackContextValue | null>(null)
24+
25+
export function useDocFeedback() {
26+
const context = React.useContext(DocFeedbackContext)
27+
return context
28+
}
29+
30+
export function useDocFeedbackRequired() {
31+
const context = useDocFeedback()
32+
if (!context) {
33+
throw new Error('useDocFeedback must be used within DocFeedbackProvider')
34+
}
35+
return context
36+
}
37+
38+
export const DocFeedbackContextProvider = DocFeedbackContext.Provider

0 commit comments

Comments
 (0)