Repository navigation
feat: RSS-SE-14 implement schema saving to supabase - #27
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughДобавлены Supabase-загрузка и автосохранение схемы редактора по пользователю, а также вынесена константа задержки debounce в редакторе Swagger. ChangesПерсистентность схемы редактора
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant HomePage
participant editor-schema.ts
participant Supabase
HomePage->>editor-schema.ts: loadEditorSchema(userId)
editor-schema.ts->>Supabase: select content by user_id
Supabase-->>editor-schema.ts: record or error
editor-schema.ts-->>HomePage: EditorSchema | null
HomePage->>editor-schema.ts: saveEditorSchema({ userId, content })
editor-schema.ts->>Supabase: upsert editor_schemas by user_id
Supabase-->>editor-schema.ts: result or error
editor-schema.ts-->>HomePage: completed
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/pages/home-page/ui/home-page.tsx (1)
46-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueОтладочный
console.logс TODO.
console.log(parsedSchema)выводит содержимое схемы на каждый рендер и помечен как временный (RSS-SE-17). Стоит удалить перед мержем или отследить задачу. Хотите, чтобы я подготовил удаление или завёл отдельный issue для RSS-SE-17?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/home-page/ui/home-page.tsx` at line 46, Remove the temporary debug logging from the home page render path: the console.log(parsedSchema) statement in home-page.tsx should not remain in production code. Locate the HomePage component and delete this log (and its TODO marker) unless you are explicitly keeping it behind a temporary, gated debug path tied to RSS-SE-17.src/features/schema-persistence/api/editor-schema.ts (1)
32-43: 🗄️ Data Integrity & Integration | 🔵 TrivialУбедитесь, что для
upsertесть уникальное ограничение наuser_id.
onConflict: 'user_id'работает только при наличии уникального (или PK) ограничения на колонкуuser_idв таблицеeditor_schemas. Без него upsert будет создавать дубликаты строк вместо обновления, и последующийloadEditorSchemaс.maybeSingle()начнёт падать при нескольких строках на пользователя. Проверьте, что миграция создаёт такое ограничение.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/schema-persistence/api/editor-schema.ts` around lines 32 - 43, `saveEditorSchema` relies on `upsert(..., { onConflict: 'user_id' })`, so ensure the `editor_schemas` table has a unique or primary key constraint on `user_id`. Update the relevant migration/schema definition to add that constraint, and verify it matches the conflict target used by `saveEditorSchema` so updates happen instead of duplicate rows being inserted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/home-page/ui/home-page.tsx`:
- Around line 34-44: In the home page effect in home-page.tsx, add explicit
error handling for the saveEditorSchema async call so the promise rejection is
caught instead of being left as an unhandled failure, and update the dependency
list to track user?.id rather than the full user object. Use the existing
useEffect around detectFormat/saveEditorSchema as the place to apply both fixes,
and keep the auto-save guard aligned with the loaded schema behavior so it
doesn’t re-save unnecessarily after initial load.
- Around line 20-26: The useEffect in home-page.tsx calls
loadEditorSchema(user.id) without handling rejections, so add error handling to
prevent an unhandled promise rejection. Update the logic around loadEditorSchema
and its then(saved) flow to include a catch path (or equivalent async error
handling) that logs or otherwise handles the error safely, while keeping the
existing setRawText(saved.content) behavior when a schema is successfully
loaded.
---
Nitpick comments:
In `@src/features/schema-persistence/api/editor-schema.ts`:
- Around line 32-43: `saveEditorSchema` relies on `upsert(..., { onConflict:
'user_id' })`, so ensure the `editor_schemas` table has a unique or primary key
constraint on `user_id`. Update the relevant migration/schema definition to add
that constraint, and verify it matches the conflict target used by
`saveEditorSchema` so updates happen instead of duplicate rows being inserted.
In `@src/pages/home-page/ui/home-page.tsx`:
- Line 46: Remove the temporary debug logging from the home page render path:
the console.log(parsedSchema) statement in home-page.tsx should not remain in
production code. Locate the HomePage component and delete this log (and its TODO
marker) unless you are explicitly keeping it behind a temporary, gated debug
path tied to RSS-SE-17.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7e6e1672-ee4b-4ac1-a6a2-ca0791360d1b
📒 Files selected for processing (2)
src/features/schema-persistence/api/editor-schema.tssrc/pages/home-page/ui/home-page.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/home-page/ui/home-page.tsx`:
- Around line 23-38: `useEffect` in `HomePage` starts loading before
`skipAutosave` is set, which lets `saveEditorSchema` write an empty
`debouncedText` or the previous user’s content. Move `skipAutosave.current =
true` to the start of the effect in `home-page.tsx`, before `loadSchema`/`await
loadEditorSchema(user.id)`, so autosave is disabled immediately when `user.id`
becomes available.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b4d242b2-748e-4c23-81a8-0005e3c9067d
📒 Files selected for processing (2)
src/pages/home-page/ui/home-page.tsxsrc/widgets/swagger-editor/ui/swagger-editor.tsx
✅ Files skipped from review due to trivial changes (1)
- src/widgets/swagger-editor/ui/swagger-editor.tsx
| useEffect(() => { | ||
| if (!user?.id) return; | ||
|
|
||
| const loadSchema = async () => { | ||
| try { | ||
| const savedSchema = await loadEditorSchema(user.id); | ||
| if (savedSchema) { | ||
| skipAutosave.current = true; | ||
| setRawText(savedSchema.content); | ||
| } | ||
| } catch (error) { | ||
| console.error('Failed to load saved schema:', error); | ||
| } | ||
| }; | ||
|
|
||
| void loadSchema(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== auth-provider ==="
fd -t f 'auth-provider' src/features/auth --exec cat -n {}
echo "=== initial user state / useState in auth ==="
rg -nP 'useState|user\s*[:=]|createContext' src/features/auth -C2Repository: FierceSloth/swagger-editor-app
Length of output: 3032
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== home-page outline ==="
ast-grep outline src/pages/home-page/ui/home-page.tsx --view expanded
echo "=== home-page relevant section ==="
cat -n src/pages/home-page/ui/home-page.tsx | sed -n '1,220p'
echo "=== auth provider + usage of initialUser ==="
rg -n "AuthProvider|initialUser|useAuth\(" src -C 2Repository: FierceSloth/swagger-editor-app
Length of output: 5399
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== debounce hook ==="
fd -t f 'use-debounce' src shared --exec cat -n {} \;
echo "=== editor schema persistence api ==="
fd -t f 'editor-schema' src --exec cat -n {} \;
echo "=== auth provider server/client initialization path ==="
rg -n "initialUser|AuthProvider|useAuth" src/app src/pages src/features -C 2Repository: FierceSloth/swagger-editor-app
Length of output: 4761
src/pages/home-page/ui/home-page.tsx:23-38 — установите skipAutosave до запуска загрузки — saveEditorSchema успевает записать пустой debouncedText сразу после появления user.id, а skipAutosave.current = true выставляется только после await. Это может затереть сохранённую схему пустым content, а при смене пользователя — записать контент предыдущего пользователя под новым user_id. Перенесите флаг в начало эффекта загрузки.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/home-page/ui/home-page.tsx` around lines 23 - 38, `useEffect` in
`HomePage` starts loading before `skipAutosave` is set, which lets
`saveEditorSchema` write an empty `debouncedText` or the previous user’s
content. Move `skipAutosave.current = true` to the start of the effect in
`home-page.tsx`, before `loadSchema`/`await loadEditorSchema(user.id)`, so
autosave is disabled immediately when `user.id` becomes available.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== current home-page.tsx ==="
cat -n src/pages/home-page/ui/home-page.tsx
echo "=== git log for this file ==="
git log --oneline -5 -- src/pages/home-page/ui/home-page.tsxLength of output: 8982
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== auth-provider full ==="
fd -t f 'auth-provider' src/features/auth --exec cat -n {}
echo "=== diff of fix commit ==="
git show 6bb6a58 -- src/pages/home-page/ui/home-page.tsx src/features/schema-persistence/api/editor-schema.tsLength of output: 5734
@dilmun1101, thanks for the update — but looking at commit 6bb6a58, it looks like the skipAutosave guard was removed entirely rather than reordered, which brings back (and actually widens) the original race condition.
Here's why it's still a problem:
AuthProviderinitializesuserfrominitialUser(Line 19 insrc/features/auth/model/auth-provider.tsx), souser.idcan already be truthy on the very first render for an already-authenticated user (e.g. after a page refresh with a restored session).useDebounceinitializes its internal state with the incoming value (Line 4 insrc/shared/lib/hooks/use-debounce.ts), so on first renderdebouncedText === rawText === ''.- That means the autosave
useEffect(Lines 44-59 ofhome-page.tsx) can fire immediately withcontent: '', callingsaveEditorSchemaand overwriting the user's previously saved schema with an empty string — before the load effect'sawait loadEditorSchema(user.id)even resolves.
So the fix removed the protection rather than fixing its ordering. I'd suggest bringing back a guard, but set it synchronously so it's true before the async load starts (not after the await), e.g.:
const skipAutosave = useRef(false);
useEffect(() => {
if (!user?.id) return;
skipAutosave.current = true; // set before the async call starts
const loadSchema = async () => {
try {
const savedSchema = await loadEditorSchema(user.id);
if (savedSchema) {
setRawText(savedSchema.content);
} else {
skipAutosave.current = false; // nothing to skip if there's no saved content
}
} catch (error) {
console.error('Failed to load saved schema:', error);
skipAutosave.current = false;
}
};
void loadSchema();
}, [user?.id]);
useEffect(() => {
if (!user?.id) return;
if (skipAutosave.current) {
skipAutosave.current = false;
return;
}
const saveSchema = async () => {
try {
await saveEditorSchema({ userId: user.id, content: debouncedText });
} catch (error) {
console.error('Failed to autosave schema:', error);
}
};
void saveSchema();
}, [user?.id, debouncedText]);This restores the guard against clobbering saved content, and sets it synchronously so it also covers the "already-authenticated on mount" case, not just the user-switch case.
🧬 Code Graph Analysis Results
src/pages/home-page/ui/home-page.tsx (file)
Lines 1-78 (full context)
'use client';
import { useMemo, useState, useEffect } from 'react';
import { parseToObject } from '`@/shared/lib/parse-to-object`';
import { SwaggerEditor } from '`@/widgets/swagger-editor`';
import { useAuth } from '`@/features/auth`';
import { loadEditorSchema, saveEditorSchema } from '`@/features/schema-persistence/api/editor-schema`';
import { useDebounce } from '`@/shared/lib/hooks`';
import styles from './home-page.module.scss';
const DEBOUNCE_DELAY_SIZE_MS = 1_000; // 1 second
export function HomePage() {
const { user } = useAuth();
const [rawText, setRawText] = useState('');
const [isSchemaValid, setIsSchemaValid] = useState(false);
const debouncedText = useDebounce(rawText, DEBOUNCE_DELAY_SIZE_MS);
useEffect(() => {
if (!user?.id) return;
const loadSchema = async () => {
try {
const savedSchema = await loadEditorSchema(user.id);
if (savedSchema) {
setRawText(savedSchema.content);
}
} catch (error) {
console.error('Failed to load saved schema:', error);
}
};
void loadSchema();
}, [user?.id]);
const parsedSchema = useMemo(() => {
if (!isSchemaValid || !rawText) return null;
return parseToObject(rawText);
}, [rawText, isSchemaValid]);
useEffect(() => {
if (!user?.id) return;
const saveSchema = async () => {
try {
await saveEditorSchema({
userId: user.id,
content: debouncedText,
});
} catch (error) {
console.error('Failed to autosave schema:', error);
}
};
void saveSchema();
}, [user?.id, debouncedText]);
console.log(parsedSchema); // TODO: Remove after add Swagger Viewer (<RSS-SE-17>)
return (
<div className={styles.container}>
<div className={styles.editor}>
<SwaggerEditor value={rawText} onChange={setRawText} onValidationChange={setIsSchemaValid} />
</div>
<div className={styles.viewer}>
<div className={styles.stub}>
<h2>Very Cool SwaggerViewer</h2>
</div>
</div>
</div>
);
}src/features/schema-persistence/api/editor-schema.ts
Lines 13-20
export async function loadEditorSchema(userId: string): Promise<EditorSchema | null> {
const { data, error } = await supabase.from('editor_schemas').select('content').eq('user_id', userId).maybeSingle();
if (error) throw error;
if (!isEditorSchema(data)) return null;
return data;
}Lines 22-32
export async function saveEditorSchema(params: { userId: string; content: string }) {
const { error } = await supabase.from('editor_schemas').upsert(
{
user_id: params.userId,
content: params.content,
},
{ onConflict: 'user_id' }
);
if (error) throw error;
}src/widgets/swagger-editor/ui/swagger-editor.tsx
Lines 23-74
export function SwaggerEditor({ value = '', onChange, onValidationChange }: IProps) {
const debouncedValue = useDebounce(value, DEBOUNCE_DELAY_SIZE_MS);
const currentFormat = useMemo(() => detectFormat(debouncedValue), [debouncedValue]);
const [errorsCount, setErrorsCount] = useState(0);
const [warningsCount, setWarningsCount] = useState(0);
const isValid = errorsCount === 0 && currentFormat !== null && value.trim().length > 0;
useEffect(() => {
onValidationChange?.(isValid);
}, [errorsCount, currentFormat, onValidationChange, isValid]);
const onFormatToggle = (newFormat: DataFormat) => {
if (newFormat === currentFormat) return;
const convertedText = convertFormat(value, newFormat);
if (convertedText !== null) {
onChange?.(convertedText);
}
};
const onEditorUpdate = useCallback((viewUpdate: ViewUpdate) => {
let errors = 0;
let warnings = 0;
forEachDiagnostic(viewUpdate.state, (diag) => {
if (diag.severity === 'error') errors++;
if (diag.severity === 'warning') warnings++;
});
setErrorsCount(errors);
setWarningsCount(warnings);
}, []);
return (
<div className={styles.wrapper}>
<div className={styles.toolbar}>
<FormatToggle format={currentFormat} onToggle={onFormatToggle} />
<ValidationStatus isValid={isValid} />
</div>
<CodeEditor
value={value}
height="100%"
onChange={onChange}
format={currentFormat ?? undefined}
className={styles.editor}
extensions={[openapiLinter]}
onUpdate={onEditorUpdate}
/>
<ValidationFeedback errorsCount={errorsCount} warningsCount={warningsCount} />
</div>
);
}src/shared/lib/hooks/use-debounce.ts
Lines 3-17
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}src/shared/lib/parse-to-object.ts
Lines 3-14
export function parseToObject(text: string): Record<string, unknown> | null {
try {
const parsed = yaml.parse(text) as unknown;
if (typeof parsed === 'object' && parsed !== null) {
return parsed as Record<string, unknown>;
}
} catch {
// not valid YAML/JSON
}
return null;
}src/features/auth/model/auth-provider.tsx
Lines 38-46
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
📋 Trello Task ID
⚡️ Summary
Implemented a schemas table, autosave for authenticated users, and automatic schema loading on sign-in.
🛠 Type of change
feat(New feature)fix(Bug fix)refactor(Code improvement / Refactoring without changing logic)style(Formatting, CSS)docs(Documentation)chore(Configs, Build)test(Testing)📷 Screenshots / GIFs
Summary by CodeRabbit