Skip to content

feat: RSS-SE-14 implement schema saving to supabase - #27

Merged
dilmun1101 merged 6 commits into
developfrom
story/RSS-SE-14_implementSchemaSavingToSupabase
Jul 8, 2026
Merged

dilmun1101 merged 6 commits into
developfrom
story/RSS-SE-14_implementSchemaSavingToSupabase

Conversation

@dilmun1101

@dilmun1101 dilmun1101 commented Jul 8, 2026 •

Copy link
Copy Markdown
Collaborator

📋 Trello Task ID

  • RSS-SE-14 (Task ID / Trello Card)
  • No ID (Global fix or chore)

⚡️ 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

Снимок экрана 2026-07-08 164749

Summary by CodeRabbit

  • New Features
    • Добавлена персистентность схемы редактора для пользователей: при входе сохраняется и восстанавливается содержимое.
    • На странице редактора теперь автоматически подгружается ранее сохранённый текст схемы.
    • Включено автосохранение изменений с задержкой (debounce) при редактировании.
  • Chores
    • Уточнена задержка автосохранения для стабильного поведения редактора (используется единая константа).

@vercel

vercel Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
swagger-editor-app Ready Ready Preview, Comment Jul 8, 2026 4:36pm

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ded8523-bd88-4a0f-9acb-d59a33069274

📥 Commits

Reviewing files that changed from the base of the PR and between 65004f5 and 6bb6a58.

📒 Files selected for processing (2)
  • src/features/schema-persistence/api/editor-schema.ts
  • src/pages/home-page/ui/home-page.tsx

📝 Walkthrough

Walkthrough

Добавлены Supabase-загрузка и автосохранение схемы редактора по пользователю, а также вынесена константа задержки debounce в редакторе Swagger.

Changes

Персистентность схемы редактора

Layer / File(s) Summary
API загрузки и сохранения схемы через Supabase
src/features/schema-persistence/api/editor-schema.ts
Обновлены EditorSchema, loadEditorSchema и saveEditorSchema для хранения только content в editor_schemas.
Загрузка и автосохранение в HomePage
src/pages/home-page/ui/home-page.tsx
HomePage загружает rawText по user.id и сохраняет debouncedText через Supabase с useAuth.
Константа задержки debounce
src/widgets/swagger-editor/ui/swagger-editor.tsx
SwaggerEditor использует именованную константу DEBOUNCE_DELAY_SIZE_MS вместо литерала 500 для useDebounce.

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
Loading

Possibly related PRs

Suggested reviewers: a-rudak-EIS, lexarudak

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Заголовок кратко и точно описывает основное изменение: сохранение схемы в Supabase.
Description check ✅ Passed Описание соответствует шаблону: есть Trello ID, краткое summary, тип изменения и скриншот для UI-правки.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch story/RSS-SE-14_implementSchemaSavingToSupabase

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9004e1a and f3b86e7.

📒 Files selected for processing (2)
  • src/features/schema-persistence/api/editor-schema.ts
  • src/pages/home-page/ui/home-page.tsx

Comment thread src/pages/home-page/ui/home-page.tsx Outdated
Comment thread src/pages/home-page/ui/home-page.tsx Outdated
FierceSloth
FierceSloth previously approved these changes Jul 8, 2026

@FierceSloth FierceSloth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

@dilmun1101
dilmun1101 requested a review from lexarudak July 8, 2026 12:57
Comment thread src/pages/home-page/ui/home-page.tsx Outdated
Comment thread src/pages/home-page/ui/home-page.tsx Outdated
Comment thread src/pages/home-page/ui/home-page.tsx Outdated
Comment thread src/pages/home-page/ui/home-page.tsx Outdated
Comment thread src/pages/home-page/ui/home-page.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 01193e3 and c6b2f76.

📒 Files selected for processing (2)
  • src/pages/home-page/ui/home-page.tsx
  • src/widgets/swagger-editor/ui/swagger-editor.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/widgets/swagger-editor/ui/swagger-editor.tsx

Comment on lines +23 to +38
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();

@coderabbitai coderabbitai Bot Jul 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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 -C2

Repository: 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 2

Repository: 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 2

Repository: 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 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.tsx

Length 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.ts

Length 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:

  • AuthProvider initializes user from initialUser (Line 19 in src/features/auth/model/auth-provider.tsx), so user.id can already be truthy on the very first render for an already-authenticated user (e.g. after a page refresh with a restored session).
  • useDebounce initializes its internal state with the incoming value (Line 4 in src/shared/lib/hooks/use-debounce.ts), so on first render debouncedText === rawText === ''.
  • That means the autosave useEffect (Lines 44-59 of home-page.tsx) can fire immediately with content: '', calling saveEditorSchema and overwriting the user's previously saved schema with an empty string — before the load effect's await 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;
}

@dilmun1101
dilmun1101 merged commit b8cea13 into develop Jul 8, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 6bb6a58a Deployed Jul 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants