summaryrefslogtreecommitdiff
path: root/apps/web/app/reader
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-09 23:48:27 -0800
committerFuwn <[email protected]>2026-02-09 23:48:27 -0800
commitd0d49d9b759f841c0a05b2410efbd26957a813dc (patch)
tree6c72bd6a27e13b6cadb2d1797afedc172ffc32e7 /apps/web/app/reader
parentfix: P0 correctness and security fixes (diff)
downloadasa.news-d0d49d9b759f841c0a05b2410efbd26957a813dc.tar.xz
asa.news-d0d49d9b759f841c0a05b2410efbd26957a813dc.zip
fix: P0 correctness/security fixes and P1 lint error resolution
P0: add missing 'developer' case to check_custom_feed_limit trigger, scope user_entry_states join to authenticated user in API v1 entries, replace in-memory rate limiting with Supabase-backed check_rate_limit RPC. P1: fix all 9 ESLint errors — useSyncExternalStore for useIsMobile, restructure WebhookSection to avoid set-state-in-effect, move ref mutations into useEffect, replace <a> with <Link> on shared page, ignore generated public/sw.js in eslint config.
Diffstat (limited to 'apps/web/app/reader')
-rw-r--r--apps/web/app/reader/_components/entry-detail-panel.tsx2
-rw-r--r--apps/web/app/reader/_components/reader-layout-shell.tsx10
-rw-r--r--apps/web/app/reader/_components/reader-shell.tsx5
-rw-r--r--apps/web/app/reader/_components/search-overlay.tsx9
-rw-r--r--apps/web/app/reader/settings/_components/api-settings.tsx44
5 files changed, 44 insertions, 26 deletions
diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx
index 6982083..9848d3f 100644
--- a/apps/web/app/reader/_components/entry-detail-panel.tsx
+++ b/apps/web/app/reader/_components/entry-detail-panel.tsx
@@ -186,7 +186,7 @@ export function EntryDetailPanel({
}
}
- setUnpositionedHighlights(failedHighlights)
+ queueMicrotask(() => setUnpositionedHighlights(failedHighlights))
}, [sanitisedContent, highlightsData])
const handleTextSelection = useCallback(() => {
diff --git a/apps/web/app/reader/_components/reader-layout-shell.tsx b/apps/web/app/reader/_components/reader-layout-shell.tsx
index fe158b5..391e6a4 100644
--- a/apps/web/app/reader/_components/reader-layout-shell.tsx
+++ b/apps/web/app/reader/_components/reader-layout-shell.tsx
@@ -162,9 +162,15 @@ export function ReaderLayoutShell({
const sidebarGroupRef = useGroupRef()
const sidebarMaxWidthRef = useRef(sidebarMaxWidth)
- sidebarMaxWidthRef.current = sidebarMaxWidth
const sidebarOnLayoutChangedRef = useRef(sidebarLayout.onLayoutChanged)
- sidebarOnLayoutChangedRef.current = sidebarLayout.onLayoutChanged
+
+ useEffect(() => {
+ sidebarMaxWidthRef.current = sidebarMaxWidth
+ }, [sidebarMaxWidth])
+
+ useEffect(() => {
+ sidebarOnLayoutChangedRef.current = sidebarLayout.onLayoutChanged
+ }, [sidebarLayout.onLayoutChanged])
useEffect(() => {
useUserInterfaceStore.getState().setResetSidebarLayout(() => {
diff --git a/apps/web/app/reader/_components/reader-shell.tsx b/apps/web/app/reader/_components/reader-shell.tsx
index 8a5a044..65a4900 100644
--- a/apps/web/app/reader/_components/reader-shell.tsx
+++ b/apps/web/app/reader/_components/reader-shell.tsx
@@ -67,7 +67,10 @@ export function ReaderShell({
const detailGroupRef = useGroupRef()
const detailOnLayoutChangedRef = useRef(detailLayout.onLayoutChanged)
- detailOnLayoutChangedRef.current = detailLayout.onLayoutChanged
+
+ useEffect(() => {
+ detailOnLayoutChangedRef.current = detailLayout.onLayoutChanged
+ }, [detailLayout.onLayoutChanged])
useEffect(() => {
useUserInterfaceStore.getState().setResetDetailLayout(() => {
diff --git a/apps/web/app/reader/_components/search-overlay.tsx b/apps/web/app/reader/_components/search-overlay.tsx
index 5cfdb57..11e709c 100644
--- a/apps/web/app/reader/_components/search-overlay.tsx
+++ b/apps/web/app/reader/_components/search-overlay.tsx
@@ -50,10 +50,6 @@ export function SearchOverlay({ onClose }: SearchOverlayProperties) {
}, [])
useEffect(() => {
- setSelectedResultIndex(-1)
- }, [searchQuery])
-
- useEffect(() => {
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
onClose()
@@ -122,7 +118,10 @@ export function SearchOverlay({ onClose }: SearchOverlayProperties) {
ref={inputReference}
type="text"
value={searchQuery}
- onChange={(event) => setSearchQuery(event.target.value)}
+ onChange={(event) => {
+ setSearchQuery(event.target.value)
+ setSelectedResultIndex(-1)
+ }}
onKeyDown={handleInputKeyDown}
placeholder="search entries..."
className="w-full bg-transparent text-text-primary outline-none placeholder:text-text-dim"
diff --git a/apps/web/app/reader/settings/_components/api-settings.tsx b/apps/web/app/reader/settings/_components/api-settings.tsx
index cca673f..102e3fe 100644
--- a/apps/web/app/reader/settings/_components/api-settings.tsx
+++ b/apps/web/app/reader/settings/_components/api-settings.tsx
@@ -1,9 +1,8 @@
"use client"
-import { useState, useEffect } from "react"
+import { useState } from "react"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { useUserProfile } from "@/lib/queries/use-user-profile"
-import { queryKeys } from "@/lib/queries/query-keys"
import { notify } from "@/lib/notify"
interface ApiKey {
@@ -293,21 +292,36 @@ function ApiKeysSection() {
)
}
-function WebhookSection() {
+function WebhookSectionLoading() {
const { data: webhookConfig, isLoading } = useWebhookConfig()
+
+ if (isLoading) {
+ return <p className="text-text-dim">loading webhook configuration ...</p>
+ }
+
+ if (!webhookConfig) {
+ return <p className="text-text-dim">failed to load webhook configuration</p>
+ }
+
+ return <WebhookSection initialConfiguration={webhookConfig} />
+}
+
+function WebhookSection({
+ initialConfiguration,
+}: {
+ initialConfiguration: WebhookConfiguration
+}) {
+ const { data: webhookConfig } = useWebhookConfig()
const updateWebhookConfig = useUpdateWebhookConfig()
const testWebhook = useTestWebhook()
- const [webhookUrl, setWebhookUrl] = useState("")
- const [webhookSecret, setWebhookSecret] = useState("")
+ const [webhookUrl, setWebhookUrl] = useState(
+ initialConfiguration.webhookUrl ?? ""
+ )
+ const [webhookSecret, setWebhookSecret] = useState(
+ initialConfiguration.webhookSecret ?? ""
+ )
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
- useEffect(() => {
- if (webhookConfig) {
- setWebhookUrl(webhookConfig.webhookUrl ?? "")
- setWebhookSecret(webhookConfig.webhookSecret ?? "")
- }
- }, [webhookConfig])
-
function handleSaveWebhookConfig() {
updateWebhookConfig.mutate(
{
@@ -371,10 +385,6 @@ function WebhookSection() {
setHasUnsavedChanges(true)
}
- if (isLoading) {
- return <p className="text-text-dim">loading webhook configuration ...</p>
- }
-
return (
<div className="mb-6">
<h3 className="mb-2 text-text-primary">webhooks</h3>
@@ -503,7 +513,7 @@ export function ApiSettings() {
return (
<div className="px-4 py-3">
<ApiKeysSection />
- <WebhookSection />
+ <WebhookSectionLoading />
<div>
<h3 className="mb-2 text-text-primary">api documentation</h3>