Development 03#3
Conversation
…"Go Back" button in WikiEditor and update dependencies
…move unused SignUp button
…orts in ArticlesList and WikiEditor
… and update dependencies
…dd TextType component for animated text display; update dependencies
…handling and improve error logging in WikiArticleViewer
…ponent for enhanced user interaction in WikiArticleViewer
…nd WikiEditor; update layout to include Toaster component
…deleteArticleForm and add user authorization check in ViewArticlePage
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…tip component consistency
There was a problem hiding this comment.
Pull request overview
This PR adds UI enhancements and an AI-powered article summarization flow to the wiki article viewer, along with some routing + navbar updates and improved toasts/UX around editing/deleting.
Changes:
- Add
/api/airoute that calls OpenRouter to summarize article content (with Zod request validation). - Add new UI components (tooltip, shiny animated text, typewriter text) and integrate them into the article viewer summary experience.
- Update routing/nav/editor UX (new
Routesentries, navbar “New Article” link, toasts instead of alerts, authz-basedcanEdit).
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| types/index.ts | Adds new route constants (HOME, ARTICLES). |
| pnpm-lock.yaml | Locks newly added dependencies. |
| package.json | Adds dependencies for AI + animations + toasts + Zod v4. |
| components/ui/tooltip.tsx | Introduces Base UI tooltip wrappers used across the app. |
| components/ui/text-type.jsx | Adds a client typewriter component used to animate summaries. |
| components/ui/shiny-text.jsx | Adds a motion-based “shiny text” animation component. |
| components/ui/link-button.tsx | Replaces dedicated sign-in/out buttons with a generic link-button wrapper. |
| components/ui/button.tsx | Removes the default nativeButton={false} override. |
| components/features/wikicards/wiki-editor.tsx | Replaces alerts/confirm with sonner toasts; adds “Go Back” link button. |
| components/features/wikicards/wiki-card.tsx | Adds line-clamp-4 for article summary display. |
| components/features/wikicards/wiki-article-viewer.tsx | Adds summarization UI, tooltips, copy-to-clipboard, and useActionState delete handling. |
| components/features/wikicards/articles-list.tsx | Removes the “New Article” button from the list header. |
| components/features/navbar/signup-button.tsx | Removes the old sign-up button component. |
| components/features/navbar/navbar.tsx | Uses LinkButton + Routes; adds “New Article” for authenticated users. |
| components/features/navbar/index.ts | Stops exporting removed navbar button components. |
| components.json | Adds a registry entry and reorders menu config properties. |
| app/wiki/[id]/page.tsx | Uses authorizeUserToEditArticle to compute canEdit. |
| app/layout.tsx | Adds global TooltipProvider wrapper and Sonner Toaster. |
| app/api/ai/route.ts | Adds AI summarization API route. |
| app/actions/articles.ts | Updates deleteArticleForm signature to support useActionState and return an error state. |
| .vscode/settings.json | Adds gsap to spellchecker words list. |
| .github/workflows/main.yml | Adds AI_GATEWAY_API_KEY to workflow env. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
components/ui/link-button.tsx:12
LinkButtonis typed asReact.FC, butReactisn’t imported in this file. This will fail typechecking.
Import type React/FC from react, or switch to const LinkButton = ({...}: LinkButtonProps) => { ... } without referencing React.FC.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const user = await stackServerApp.getUser({ or: "redirect" }); | ||
| // Mock permission check - in a real app, this would come from auth/user context | ||
| const canEdit = true; // Set to true for demonstration | ||
| const article = await getArticleByIdFromDB(id); | ||
| const userId = user?.id ?? "mockUserId"; | ||
| const [article, canEdit] = await Promise.all([ | ||
| getArticleByIdFromDB(id), | ||
| authorizeUserToEditArticle(userId, id), | ||
| ]); |
There was a problem hiding this comment.
stackServerApp.getUser({ or: "redirect" }) should guarantee a user, but this still falls back to a hard-coded "mockUserId". That can mask auth issues and may cause incorrect authorization results.
Prefer treating user as non-null after redirect (or explicitly handle the null case with a redirect/throw) and pass user.id directly.
| "@aws-sdk/client-s3": "^3.993.0", | ||
| "@base-ui/react": "^1.2.0", | ||
| "@neondatabase/serverless": "^1.0.2", | ||
| "@openrouter/sdk": "^0.8.0", |
There was a problem hiding this comment.
@openrouter/sdk is added as a dependency but isn’t referenced anywhere in the codebase (the API route uses fetch directly). Keeping unused deps increases install size and supply-chain surface.
Either remove @openrouter/sdk from dependencies or switch the AI route to use the SDK so the dependency is justified.
| "@openrouter/sdk": "^0.8.0", |
| import { | ||
| motion, | ||
| useAnimationFrame, | ||
| useMotionValue, | ||
| useTransform, | ||
| } from "motion/react"; | ||
| import { useCallback, useEffect, useRef, useState } from "react"; | ||
|
|
There was a problem hiding this comment.
This component uses React hooks, but the file is missing the "use client" directive. In the App Router this will cause a build/runtime error when imported into client components.
Add "use client" at the top of the file (before imports) or convert it to a server-safe component (no hooks).
|
|
||
| setCurrentTextIndex((prev) => (prev + 1) % textArray.length); | ||
| setCurrentCharIndex(0); | ||
| timeout = setTimeout(() => {}, pauseDuration); |
There was a problem hiding this comment.
The deletion->next-sentence transition sets timeout = setTimeout(() => {}, pauseDuration) which is a no-op. This both adds dead code and means pauseDuration isn’t actually used between sentences after deletion.
Either remove the no-op timeout or use it to schedule the next typing step after pauseDuration (so the pause is honored).
| timeout = setTimeout(() => {}, pauseDuration); | |
| timeout = setTimeout(() => { | |
| executeTypingAnimation(); | |
| }, pauseDuration); |
| }; | ||
| useEffect(() => { | ||
| let isMounted = true; | ||
|
|
||
| const fetchPageView = async () => { | ||
| const newCount = await incrementPageViews(article.id); | ||
| setLocalPageViews(newCount); | ||
|
|
||
| if (hasSessionStorage) { | ||
| window.sessionStorage.setItem(storageKey, "true"); | ||
| if (isMounted) { | ||
| setLocalPageViews(newCount); | ||
| } | ||
| }; | ||
|
|
||
| fetchPageView(); | ||
|
|
||
| return () => { | ||
| isMounted = false; | ||
| }; |
There was a problem hiding this comment.
incrementPageViews is now called on every mount with no per-session guard. Since incrementPageViews can trigger milestone emails, refreshing the page (or navigating back/forth) can inflate counts and spam milestone notifications.
Consider restoring the session-based dedupe (e.g., sessionStorage key) or implement server-side rate limiting/deduplication keyed by (user/session, article).
| variant={"default"} | ||
| render={(props) => ( | ||
| <Link | ||
| href={!isEditing ? Routes.HOME : `/wiki/${articleId}`} |
There was a problem hiding this comment.
href can become /wiki/undefined when isEditing is true but articleId is missing (it’s optional in props). This produces a broken back link.
Guard the editing case (e.g., only render the article link when articleId is present, otherwise fall back to Routes.HOME).
| href={!isEditing ? Routes.HOME : `/wiki/${articleId}`} | |
| href={!isEditing || !articleId ? Routes.HOME : `/wiki/${articleId}`} |
| } finally { | ||
| setIsSummarizing(false); | ||
| } | ||
| return () => controller.abort(); // ✅ return cleanup function to abort on unmount |
There was a problem hiding this comment.
handleSummarizeArticle creates an AbortController and returns a cleanup function, but returning a function from an event handler has no effect—so the request is never aborted on unmount. The handler can also still call setState after unmount.
If you want abort-on-unmount or abort on repeated clicks, store the controller in a ref and abort it in a useEffect cleanup (and/or before starting a new request). Otherwise, remove the controller/returned cleanup to avoid implying it’s handled.
| return () => controller.abort(); // ✅ return cleanup function to abort on unmount |
| {/* Article Content */} | ||
| <Card> | ||
| <CardContent className="pt-6"> | ||
| <CardContent className="pt-20 relative max-h-150 overflow-auto"> |
There was a problem hiding this comment.
max-h-150 isn’t a standard Tailwind utility (and it’s not defined anywhere else in the repo). This will likely be a no-op and the overflow behavior won’t apply.
Use an arbitrary value (max-h-[...]) or a standard max-height token (e.g. max-h-screen, max-h-[600px], etc.).
| <CardContent className="pt-20 relative max-h-150 overflow-auto"> | |
| <CardContent className="pt-20 relative max-h-[150px] overflow-auto"> |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@halilibrahimcelik I've opened a new pull request, #4, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: halilibrahimcelik <92088301+halilibrahimcelik@users.noreply.github.com>
Co-authored-by: halilibrahimcelik <92088301+halilibrahimcelik@users.noreply.github.com>
Sync pnpm-lock.yaml after removing @openrouter/sdk
No description provided.