Repository: liam-hq/liam
Pull request #1385: ✨ Add OverrideEditor component and useYamlEditor hook for YAML editing functionality
Author: login: junkisai
Labels: name: Review effort 2/5
Created: 2025-04-17T10:23:56Z | Merged: 2025-04-18T01:50:29Z

Implemented the base functionality and styling for the `OverrideEditor`.  
CodeMirror was adopted as the editor library.

Not included in this implementation:

- Conditional rendering when toggling the diff view on/off  
- Initial display of the `schema-override.yml` content  
- Synchronization of changes with the ERD

## What would you like reviewers to focus on?
<!-- What specific aspects are you requesting review for? -->

## Testing Verification
<!-- Please describe how you verified these changes in your local environment using text/images/video -->

https://github.com/user-attachments/assets/eabc01f1-cc9d-4e00-8a04-f7a1487022a8

## What was done
<!-- This section will be filled by PR-Agent when the Pull Request is opened -->

### 🤖 Generated by PR Agent at a340034d64655ffafc15a8239c54457b5ba749e2

- Introduced `OverrideEditor` component for YAML editing.
  - Utilized CodeMirror for the editor interface.
  - Added `useYamlEditor` hook for managing YAML content.
- Updated `SchemaPage` to integrate the `OverrideEditor`.
- Added new CSS modules for styling `SchemaPage` and `OverrideEditor`.
- Included CodeMirror and related dependencies in `package.json` and `pnpm-lock.yaml`.


## Detailed Changes
<!-- This section will be filled by PR-Agent when the Pull Request is opened -->

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>6 files</summary><table>
<tr>
  <td><strong>index.ts</strong><dd><code>Export OverrideEditor component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-91ec5ef5b43ab19c16c11d8da02635caf11293dc0bf32fd198f9d34286a69aa1">+1/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>SchemaPage.module.css</strong><dd><code>Add CSS styles for SchemaPage layout</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-5b493a104345b90858e12733a1e4338c8f721109d5619c2784af2f87f3bd692c">+5/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>OverrideEditor.module.css</strong><dd><code>Add CSS styles for OverrideEditor layout</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-5457599e4c5d77a0def9bddc1d4e783551522586cc777d7844ee627a6fac7ca0">+3/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>SchemaPage.tsx</strong><dd><code>Integrate OverrideEditor into SchemaPage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-8d2e838ac4176354fe2a4383187ce0546f83f6cc7d04f5935ddd4738a3e8b07a">+6/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>OverrideEditor.tsx</strong><dd><code>Implement OverrideEditor component with YAML editor</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-a00a6841484143201a0b83e84468a3f12a260c6a4e2e994025dc79ee31b60ca4">+13/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>useYamlEditor.tsx</strong><dd><code>Add useYamlEditor hook for YAML content management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-44353224589a1f306bb04ce18743a69316f3b45cc5972368a5f16653894abd90">+144/-0</a>&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>2 files</summary><table>
<tr>
  <td><strong>package.json</strong><dd><code>Add CodeMirror dependencies for YAML editing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-1cb683806b3ef955a38fae27b87a08febf12a8b1465dedad3a3f92c0136f132a">+8/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>pnpm-lock.yaml</strong><dd><code>Update lockfile with new CodeMirror dependencies</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/liam-hq/liam/pull/1385/files#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb">+164/-0</a>&nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

## Additional Notes
<!-- Any additional information for reviewers -->

___

> <details> <summary>  Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about Qodo Merge usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>


Diff:
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.module.css b/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.module.css
new file mode 100644
index 0000000000..1a69f2f69e
--- /dev/null
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.module.css
@@ -0,0 +1,5 @@
+.wrapper {
+  display: grid;
+  grid-template-rows: auto 1fr;
+  height: 100%;
+}
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.tsx b/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.tsx
index 10410d3b4c..dbbc79c19f 100644
--- a/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.tsx
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/SchemaPage.tsx
@@ -7,7 +7,9 @@ import * as Sentry from '@sentry/nextjs'
 import { cookies } from 'next/headers'
 import { notFound } from 'next/navigation'
 import type { ComponentProps, FC } from 'react'
+import styles from './SchemaPage.module.css'
 import { ERDEditor } from './components/ERDEditor'
+import { OverrideEditor } from './components/OverrideEditor'
 import { SchemaHeader } from './components/SchemaHeader'
 import { DEFAULT_SCHEMA_TAB, SCHEMA_TAB } from './constants'
 import { safeApplySchemaOverride } from './utils/safeApplySchemaOverride'
@@ -164,12 +166,14 @@ export const SchemaPage: FC<Props> = async ({
   })
 
   return (
-    <TabsRoot defaultValue={DEFAULT_SCHEMA_TAB}>
+    <TabsRoot defaultValue={DEFAULT_SCHEMA_TAB} className={styles.wrapper}>
       <SchemaHeader />
       <TabsContent value={SCHEMA_TAB.ERD}>
         <ERDEditor {...contentProps} />
       </TabsContent>
-      <TabsContent value={SCHEMA_TAB.EDITOR}>Override Editor</TabsContent>
+      <TabsContent value={SCHEMA_TAB.EDITOR}>
+        <OverrideEditor />
+      </TabsContent>
     </TabsRoot>
   )
 }
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.module.css b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.module.css
new file mode 100644
index 0000000000..c65200bab9
--- /dev/null
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.module.css
@@ -0,0 +1,3 @@
+.wrapper {
+  height: 100%;
+}
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.tsx b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.tsx
new file mode 100644
index 0000000000..6beab14df1
--- /dev/null
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/OverrideEditor.tsx
@@ -0,0 +1,13 @@
+'use client'
+
+import { type FC, useState } from 'react'
+import styles from './OverrideEditor.module.css'
+import { useYamlEditor } from './useYamlEditor'
+
+export const OverrideEditor: FC = () => {
+  const [doc, setDoc] = useState<string | undefined>(undefined)
+
+  const { editor } = useYamlEditor({ doc, setDoc })
+
+  return <div ref={editor} className={styles.wrapper} />
+}
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/index.ts b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/index.ts
new file mode 100644
index 0000000000..651f9b1d3c
--- /dev/null
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/index.ts
@@ -0,0 +1 @@
+export * from './OverrideEditor'
diff --git a/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
new file mode 100644
index 0000000000..c7d8921a24
--- /dev/null
+++ b/frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
@@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
+
+const myHighlightStyle = HighlightStyle.define([
+  { tag: tags.propertyName, color: '#85E89D' },
+  { tag: tags.string, color: '#C8E1FF' },
+  { tag: tags.content, color: '#C8E1FF' },
+])
+
+const yamlLinter = linter((view) => {
+  const diagnostics: Diagnostic[] = []
+
+  try {
+    const doc = parseDocument(view.state.doc.toString())
+
+    if (doc.errors.length > 0) {
+      diagnostics.push(
+        ...doc.errors.map((err) => ({
+          from: err.pos[0] ?? 0,
+          to: err.pos[1] ?? 0,
+          severity: 'error' as const,
+          message: err.message,
+        })),
+      )
+    }
+  } catch (e: unknown) {
+    diagnostics.push({
+      from: 0,
+      to: 0,
+      severity: 'error' as const,
+      message: e instanceof Error ? e.message : 'YAML parsing error',
+    })
+  }
+
+  return diagnostics
+})
+
+const extensions: Extension[] = [
+  lineNumbers(),
+  foldGutter(),
+  history(),
+  keymap.of(defaultKeymap),
+  keymap.of(historyKeymap),
+  drawSelection(),
+  yaml(),
+  lintGutter(),
+  yamlLinter,
+  syntaxHighlighting(myHighlightStyle),
+  customCursorTheme,
+]
+
+type Props = {
+  doc: string | undefined
+  setDoc: Dispatch<SetStateAction<string | undefined>>
+}
+
+export const useYamlEditor = ({ doc, setDoc }: Props) => {
+  const editor = useRef(null)
+  const [container, setContainer] = useState<HTMLDivElement>()
+  const [view, setView] = useState<EditorView>()
+
+  const updateListener = useMemo(() => {
+    return EditorView.updateListener.of((update: ViewUpdate) => {
+      if (update.docChanged) {
+        setDoc(update.state.doc.toString())
+      }
+    })
+  }, [setDoc])
+
+  useEffect(() => {
+    if (editor.current) {
+      setContainer(editor.current)
+    }
+  }, [])
+
+  useEffect(() => {
+    if (!view && container) {
+      const state = EditorState.create({
+        doc,
+        extensions: [...extensions, updateListener],
+      })
+      const viewCurrent = new EditorView({
+        state,
+        parent: container,
+      })
+      setView(viewCurrent)
+    }
+  }, [view, doc, container, updateListener])
+
+  return {
+    editor,
+  }
+}
diff --git a/frontend/apps/app/package.json b/frontend/apps/app/package.json
index 23b52eaf68..404d97f005 100644
--- a/frontend/apps/app/package.json
+++ b/frontend/apps/app/package.json
@@ -3,6 +3,13 @@
   "private": true,
   "version": "0.1.0",
   "dependencies": {
+    "@codemirror/commands": "6.8.1",
+    "@codemirror/lang-yaml": "6.1.2",
+    "@codemirror/language": "6.11.0",
+    "@codemirror/lint": "6.8.5",
+    "@codemirror/state": "6.5.2",
+    "@codemirror/view": "6.36.5",
+    "@lezer/highlight": "1.2.1",
     "@liam-hq/db": "workspace:*",
     "@liam-hq/db-structure": "workspace:*",
     "@liam-hq/erd-core": "workspace:*",
@@ -18,6 +25,7 @@
     "@trigger.dev/sdk": "3.3.17",
     "cheerio": "1.0.0",
     "clsx": "2.1.1",
+    "codemirror": "6.0.1",
     "date-fns": "4.1.0",
     "diff": "7.0.0",
     "dotenv": "16.5.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index efd0b9cb74..676c803308 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -41,6 +41,27 @@ importers:
 
   frontend/apps/app:
     dependencies:
+      '@codemirror/commands':
+        specifier: 6.8.1
+        version: 6.8.1
+      '@codemirror/lang-yaml':
+        specifier: 6.1.2
+        version: 6.1.2
+      '@codemirror/language':
+        specifier: 6.11.0
+        version: 6.11.0
+      '@codemirror/lint':
+        specifier: 6.8.5
+        version: 6.8.5
+      '@codemirror/state':
+        specifier: 6.5.2
+        version: 6.5.2
+      '@codemirror/view':
+        specifier: 6.36.5
+        version: 6.36.5
+      '@lezer/highlight':
+        specifier: 1.2.1
+        version: 1.2.1
       '@liam-hq/db':
         specifier: workspace:*
         version: link:../../packages/db
@@ -86,6 +107,9 @@ importers:
       clsx:
         specifier: 2.1.1
         version: 2.1.1
+      codemirror:
+        specifier: 6.0.1
+        version: 6.0.1
       date-fns:
         specifier: 4.1.0
         version: 4.1.0
@@ -925,6 +949,30 @@ packages:
   '@clack/prompts@0.10.1':
     resolution: {integrity: sha512-Q0T02vx8ZM9XSv9/Yde0jTmmBQufZhPJfYAg2XrrrxWWaZgq1rr8nU8Hv710BQ1dhoP8rtY7YUdpGej2Qza/cw==}
 
+  '@codemirror/autocomplete@6.18.6':
+    resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
+
+  '@codemirror/commands@6.8.1':
+    resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
+
+  '@codemirror/lang-yaml@6.1.2':
+    resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==}
+
+  '@codemirror/language@6.11.0':
+    resolution: {integrity: sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==}
+
+  '@codemirror/lint@6.8.5':
+    resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==}
+
+  '@codemirror/search@6.5.10':
+    resolution: {integrity: sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==}
+
+  '@codemirror/state@6.5.2':
+    resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
+
+  '@codemirror/view@6.36.5':
+    resolution: {integrity: sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==}
+
   '@colors/colors@1.5.0':
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
@@ -1512,6 +1560,18 @@ packages:
     peerDependencies:
       '@langchain/core': '>=0.2.21 <0.4.0'
 
+  '@lezer/common@1.2.3':
+    resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
+
+  '@lezer/highlight@1.2.1':
+    resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
+
+  '@lezer/lr@1.4.2':
+    resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
+
+  '@lezer/yaml@1.0.3':
+    resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==}
+
   '@manypkg/find-root@1.1.0':
     resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
 
@@ -1523,6 +1583,9 @@ packages:
     engines: {node: '>=18'}
     hasBin: true
 
+  '@marijn/find-cluster-break@1.0.2':
+    resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+
   '@mdx-js/mdx@3.1.0':
     resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==}
 
@@ -3944,6 +4007,9 @@ packages:
   code-block-writer@10.1.1:
     resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==}
 
+  codemirror@6.0.1:
+    resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
+
   collapse-white-space@2.1.0:
     resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
 
@@ -4068,6 +4134,9 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
+  crelt@1.0.6:
+    resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
   cronstrue@2.59.0:
     resolution: {integrity: sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==}
     hasBin: true
@@ -6982,6 +7051,9 @@ packages:
     engines: {node: '>=18.0.0'}
     hasBin: true
 
+  style-mod@4.1.2:
+    resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
+
   style-to-js@1.1.16:
     resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==}
 
@@ -7604,6 +7676,9 @@ packages:
       jsdom:
         optional: true
 
+  w3c-keyname@2.2.8:
+    resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
   watchpack@2.4.2:
     resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
     engines: {node: '>=10.13.0'}
@@ -8179,6 +8254,61 @@ snapshots:
       picocolors: 1.1.1
       sisteransi: 1.0.5
 
+  '@codemirror/autocomplete@6.18.6':
+    dependencies:
+      '@codemirror/language': 6.11.0
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+      '@lezer/common': 1.2.3
+
+  '@codemirror/commands@6.8.1':
+    dependencies:
+      '@codemirror/language': 6.11.0
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+      '@lezer/common': 1.2.3
+
+  '@codemirror/lang-yaml@6.1.2':
+    dependencies:
+      '@codemirror/autocomplete': 6.18.6
+      '@codemirror/language': 6.11.0
+      '@codemirror/state': 6.5.2
+      '@lezer/common': 1.2.3
+      '@lezer/highlight': 1.2.1
+      '@lezer/lr': 1.4.2
+      '@lezer/yaml': 1.0.3
+
+  '@codemirror/language@6.11.0':
+    dependencies:
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+      '@lezer/common': 1.2.3
+      '@lezer/highlight': 1.2.1
+      '@lezer/lr': 1.4.2
+      style-mod: 4.1.2
+
+  '@codemirror/lint@6.8.5':
+    dependencies:
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+      crelt: 1.0.6
+
+  '@codemirror/search@6.5.10':
+    dependencies:
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+      crelt: 1.0.6
+
+  '@codemirror/state@6.5.2':
+    dependencies:
+      '@marijn/find-cluster-break': 1.0.2
+
+  '@codemirror/view@6.36.5':
+    dependencies:
+      '@codemirror/state': 6.5.2
+      style-mod: 4.1.2
+      w3c-keyname: 2.2.8
+
   '@colors/colors@1.5.0':
     optional: true
 
@@ -8683,6 +8813,22 @@ snapshots:
       '@langchain/core': 0.3.44(openai@4.94.0(ws@8.18.1)(zod@3.24.2))
       js-tiktoken: 1.0.19
 
+  '@lezer/common@1.2.3': {}
+
+  '@lezer/highlight@1.2.1':
+    dependencies:
+      '@lezer/common': 1.2.3
+
+  '@lezer/lr@1.4.2':
+    dependencies:
+      '@lezer/common': 1.2.3
+
+  '@lezer/yaml@1.0.3':
+    dependencies:
+      '@lezer/common': 1.2.3
+      '@lezer/highlight': 1.2.1
+      '@lezer/lr': 1.4.2
+
   '@manypkg/find-root@1.1.0':
     dependencies:
       '@babel/runtime': 7.27.0
@@ -8712,6 +8858,8 @@ snapshots:
       - encoding
       - supports-color
 
+  '@marijn/find-cluster-break@1.0.2': {}
+
   '@mdx-js/mdx@3.1.0(acorn@8.14.1)':
     dependencies:
       '@types/estree': 1.0.7
@@ -11617,6 +11765,16 @@ snapshots:
 
   code-block-writer@10.1.1: {}
 
+  codemirror@6.0.1:
+    dependencies:
+      '@codemirror/autocomplete': 6.18.6
+      '@codemirror/commands': 6.8.1
+      '@codemirror/language': 6.11.0
+      '@codemirror/lint': 6.8.5
+      '@codemirror/search': 6.5.10
+      '@codemirror/state': 6.5.2
+      '@codemirror/view': 6.36.5
+
   collapse-white-space@2.1.0: {}
 
   color-convert@1.9.3:
@@ -11721,6 +11879,8 @@ snapshots:
 
   create-require@1.1.1: {}
 
+  crelt@1.0.6: {}
+
   cronstrue@2.59.0: {}
 
   cross-spawn@7.0.6:
@@ -15171,6 +15331,8 @@ snapshots:
       prettier: 3.5.3
       tinycolor2: 1.6.0
 
+  style-mod@4.1.2: {}
+
   style-to-js@1.1.16:
     dependencies:
       style-to-object: 1.0.8
@@ -15857,6 +16019,8 @@ snapshots:
       - tsx
       - yaml
 
+  w3c-keyname@2.2.8: {}
+
   watchpack@2.4.2:
     dependencies:
       glob-to-regexp: 0.4.1


Reviews:
user: login: junkisai
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T10:35:35Z
user: login: MH4GF
body: Wonderful!!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T01:44:50Z

Review comments:
diff_hunk: @@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
path: frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
user: login: junkisai
body: Styling for the editor section


ref. [Example: Styling - CodeMirror](https://arc.net/l/quote/vjygrqtt)
created_at: 2025-04-17T10:28:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
+
+const myHighlightStyle = HighlightStyle.define([
+  { tag: tags.propertyName, color: '#85E89D' },
+  { tag: tags.string, color: '#C8E1FF' },
+  { tag: tags.content, color: '#C8E1FF' },
+])
path: frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
user: login: junkisai
body: Styling for YAML syntax highlighting

ref. [Example: Styling - CoreMirror](https://arc.net/l/quote/imuhslja)
created_at: 2025-04-17T10:30:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
+
+const myHighlightStyle = HighlightStyle.define([
+  { tag: tags.propertyName, color: '#85E89D' },
+  { tag: tags.string, color: '#C8E1FF' },
+  { tag: tags.content, color: '#C8E1FF' },
+])
+
+const yamlLinter = linter((view) => {
+  const diagnostics: Diagnostic[] = []
+
+  try {
+    const doc = parseDocument(view.state.doc.toString())
+
+    if (doc.errors.length > 0) {
+      diagnostics.push(
+        ...doc.errors.map((err) => ({
+          from: err.pos[0] ?? 0,
+          to: err.pos[1] ?? 0,
+          severity: 'error' as const,
+          message: err.message,
+        })),
+      )
+    }
+  } catch (e: unknown) {
+    diagnostics.push({
+      from: 0,
+      to: 0,
+      severity: 'error' as const,
+      message: e instanceof Error ? e.message : 'YAML parsing error',
+    })
+  }
+
+  return diagnostics
+})
path: frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
user: login: junkisai
body: A linter that throws an error if the content is not written in YAML format

<img width="192" alt="image" src="https://github.com/user-attachments/assets/1d83754f-d4ab-40f8-a3a2-7692296bbbb3" />

created_at: 2025-04-17T10:32:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
+
+const myHighlightStyle = HighlightStyle.define([
+  { tag: tags.propertyName, color: '#85E89D' },
+  { tag: tags.string, color: '#C8E1FF' },
+  { tag: tags.content, color: '#C8E1FF' },
+])
+
+const yamlLinter = linter((view) => {
+  const diagnostics: Diagnostic[] = []
+
+  try {
+    const doc = parseDocument(view.state.doc.toString())
+
+    if (doc.errors.length > 0) {
+      diagnostics.push(
+        ...doc.errors.map((err) => ({
+          from: err.pos[0] ?? 0,
+          to: err.pos[1] ?? 0,
+          severity: 'error' as const,
+          message: err.message,
+        })),
+      )
+    }
+  } catch (e: unknown) {
+    diagnostics.push({
+      from: 0,
+      to: 0,
+      severity: 'error' as const,
+      message: e instanceof Error ? e.message : 'YAML parsing error',
+    })
+  }
+
+  return diagnostics
+})
+
+const extensions: Extension[] = [
+  lineNumbers(),
+  foldGutter(),
+  history(),
+  keymap.of(defaultKeymap),
+  keymap.of(historyKeymap),
+  drawSelection(),
+  yaml(),
+  lintGutter(),
+  yamlLinter,
+  syntaxHighlighting(myHighlightStyle),
+  customCursorTheme,
+]
path: frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
user: login: junkisai
body: ---

Explanation of the extensions added from the top:

- Displaying line numbers  
- Code folding  
- History management  
- Standard keymap support (e.g., Enter to insert a new line)  
- Support for undo with cmd+Z  
- Styling for selected lines and cursor  
- YAML syntax support  
- Highlighting lines with lint errors  
- Linter for YAML  
- Styling for syntax highlighting  
- Styling for the editor
created_at: 2025-04-17T10:35:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,144 @@
+'use client'
+
+import { defaultKeymap } from '@codemirror/commands'
+import { history, historyKeymap } from '@codemirror/commands'
+import { yaml } from '@codemirror/lang-yaml'
+import {
+  HighlightStyle,
+  foldGutter,
+  syntaxHighlighting,
+} from '@codemirror/language'
+import { type Diagnostic, lintGutter, linter } from '@codemirror/lint'
+import { EditorState, type Extension } from '@codemirror/state'
+import {
+  type ViewUpdate,
+  drawSelection,
+  keymap,
+  lineNumbers,
+} from '@codemirror/view'
+import { tags } from '@lezer/highlight'
+import { EditorView } from 'codemirror'
+import {
+  type Dispatch,
+  type SetStateAction,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react'
+import { parseDocument } from 'yaml'
+
+const customCursorTheme = EditorView.theme({
+  '.cm-gutters': {
+    borderRight: 'solid 1px var(--position-pattern-border)',
+    backgroundColor: 'var(--global-background)',
+  },
+  '.cm-lineNumbers': {
+    color: 'var(--overlay-20)',
+  },
+  '.cm-foldGutter': {
+    color: 'var(--overlay-50)',
+  },
+  '.cm-selectionBackground': {
+    background:
+      'linear-gradient(0deg, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 0%, var(--color-green-alpha-20, rgba(29, 237, 131, 0.20)) 100%), var(--global-background, #141616) !important',
+  },
+  '.cm-cursor': {
+    borderLeft: '2px solid var(--overlay-100)',
+    animation: 'slow-blink 1s steps(2, start) infinite',
+  },
+  '@keyframes slow-blink': {
+    to: { visibility: 'hidden' },
+  },
+})
+
+const myHighlightStyle = HighlightStyle.define([
+  { tag: tags.propertyName, color: '#85E89D' },
+  { tag: tags.string, color: '#C8E1FF' },
+  { tag: tags.content, color: '#C8E1FF' },
+])
+
+const yamlLinter = linter((view) => {
+  const diagnostics: Diagnostic[] = []
+
+  try {
+    const doc = parseDocument(view.state.doc.toString())
+
+    if (doc.errors.length > 0) {
+      diagnostics.push(
+        ...doc.errors.map((err) => ({
+          from: err.pos[0] ?? 0,
+          to: err.pos[1] ?? 0,
+          severity: 'error' as const,
+          message: err.message,
+        })),
+      )
+    }
+  } catch (e: unknown) {
+    diagnostics.push({
+      from: 0,
+      to: 0,
+      severity: 'error' as const,
+      message: e instanceof Error ? e.message : 'YAML parsing error',
+    })
+  }
+
+  return diagnostics
+})
+
+const extensions: Extension[] = [
+  lineNumbers(),
+  foldGutter(),
+  history(),
+  keymap.of(defaultKeymap),
+  keymap.of(historyKeymap),
+  drawSelection(),
+  yaml(),
+  lintGutter(),
+  yamlLinter,
+  syntaxHighlighting(myHighlightStyle),
+  customCursorTheme,
+]
path: frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx
user: login: MH4GF
body: Cool!!
created_at: 2025-04-18T01:43:34Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-17T10:24:02Z
body: ###  ⚠️  No Changeset found

Latest commit: a340034d64655ffafc15a8239c54457b5ba749e2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/liam-hq/liam/new/feat/override-editor?filename=.changeset/swift-paws-behave.md&value=---%0A%22%40liam-hq%2Fapp%22%3A%20patch%0A---%0A%0A%E2%9C%A8%20Add%20OverrideEditor%20component%20and%20useYamlEditor%20hook%20for%20YAML%20editiing%20functionality%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-04-17T10:24:03Z
body: [vc]: #WlVwtKB7bcEPvPBl5g8VzSmHXQ3Wx536li2zehJqaMc=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJsaWFtLWRvY3MiLCJyb290RGlyZWN0b3J5IjoiZnJvbnRlbmQvYXBwcy9kb2NzIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vcm91dGUtMDYtY29yZS9saWFtLWRvY3MvM3k1dzZaeWlYQ0JkaDF2TDVVRzVnbmMyYmdEVSIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIn0seyJuYW1lIjoibGlhbS1lcmQtc2FtcGxlIiwicm9vdERpcmVjdG9yeSI6ImZyb250ZW5kL2FwcHMvZXJkLXNhbXBsZSIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yb3V0ZS0wNi1jb3JlL2xpYW0tZXJkLXNhbXBsZS9VUlhnWFBWRnZUaWU0UDRTRWs0ZWtyM2ZTZEdpIiwicHJldmlld1VybCI6ImxpYW0tZXJkLXNhbXBsZS1naXQtZmVhdC1vdmVycmlkZS1lZGl0b3Itcm91dGUtMDYtY29yZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImxpYW0tZXJkLXNhbXBsZS1naXQtZmVhdC1vdmVycmlkZS1lZGl0b3Itcm91dGUtMDYtY29yZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6ImxpYW0tYXBwIiwicm9vdERpcmVjdG9yeSI6ImZyb250ZW5kL2FwcHMvYXBwIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JvdXRlLTA2LWNvcmUvbGlhbS1hcHAvRmdpQ3VYZWZSODdRczh1dFoybkxRZ0Q2ankyZyIsInByZXZpZXdVcmwiOiJsaWFtLWFwcC1naXQtZmVhdC1vdmVycmlkZS1lZGl0b3Itcm91dGUtMDYtY29yZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImxpYW0tYXBwLWdpdC1mZWF0LW92ZXJyaWRlLWVkaXRvci1yb3V0ZS0wNi1jb3JlLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **liam-app** | ✅ Ready ([Inspect](https://vercel.com/route-06-core/liam-app/FgiCuXefR87Qs8utZ2nLQgD6jy2g)) | [Visit Preview](https://liam-app-git-feat-override-editor-route-06-core.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/liam-app-git-feat-override-editor-route-06-core.vercel.app?via=pr-comment-feedback-link) | Apr 17, 2025 10:28am |
| **liam-erd-sample** | ✅ Ready ([Inspect](https://vercel.com/route-06-core/liam-erd-sample/URXgXPVFvTie4P4SEk4ekr3fSdGi)) | [Visit Preview](https://liam-erd-sample-git-feat-override-editor-route-06-core.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/liam-erd-sample-git-feat-override-editor-route-06-core.vercel.app?via=pr-comment-feedback-link) | Apr 17, 2025 10:28am |

<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **liam-docs** | ⬜️ Ignored ([Inspect](https://vercel.com/route-06-core/liam-docs/3y5w6ZyiXCBdh1vL5UG5gnc2bgDU)) |  |  | Apr 17, 2025 10:28am |
</details>




author_association: NONE
user: login: supabase[bot]
created_at: 2025-04-17T10:24:36Z
body: [supa]:ubkupcuhuecxyycmdpur
**Updates to [Preview Branch (feat/override-editor) ↗︎](https://supabase.com/dashboard/project/ubkupcuhuecxyycmdpur)**

| Deployments | Status                      | Updated                   |
| ----------- | --------------------------- | ------------------------- |
| Database    | ✅ | Thu, 17 Apr 2025 10:26:40 UTC |
| Services    | ✅  | Thu, 17 Apr 2025 10:26:40 UTC  |
| APIs        | ✅      | Thu, 17 Apr 2025 10:26:40 UTC      |

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

| Tasks          | Status                       | Updated                    |
| -------------- | ---------------------------- | -------------------------- |
| Configurations | ✅ | Thu, 17 Apr 2025 10:26:46 UTC |
| Migrations     | ✅ | Thu, 17 Apr 2025 10:26:46 UTC |
| Seeding        | ✅   | Thu, 17 Apr 2025 10:26:48 UTC   |
| Edge Functions | ✅  | Thu, 17 Apr 2025 10:26:48 UTC  |

<hr/>




View logs for this [Workflow Run ↗︎](https://supabase.com/dashboard/project/ubkupcuhuecxyycmdpur/branches).
Learn more about [Supabase for Git ↗︎](https://supabase.com/docs/guides/platform/branching).

author_association: NONE
user: login: qodo-free-for-open-source-projects[bot]
created_at: 2025-04-17T10:37:33Z
body: ## PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 2 🔵🔵⚪⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br>

<details><summary><a href='https://github.com/liam-hq/liam/pull/1385/files#diff-44353224589a1f306bb04ce18743a69316f3b45cc5972368a5f16653894abd90R67-R75'><strong>Error Handling</strong></a>

The YAML linter catches parsing errors but doesn't handle potential runtime errors when accessing properties like err.pos which might be undefined in some error scenarios.
</summary>

```typescriptreact
if (doc.errors.length > 0) {
  diagnostics.push(
    ...doc.errors.map((err) => ({
      from: err.pos[0] ?? 0,
      to: err.pos[1] ?? 0,
      severity: 'error' as const,
      message: err.message,
    })),
  )
```

</details>

<details><summary><a href='https://github.com/liam-hq/liam/pull/1385/files#diff-a00a6841484143201a0b83e84468a3f12a260c6a4e2e994025dc79ee31b60ca4R8-R10'><strong>Missing Initialization</strong></a>

The editor is initialized with undefined document content but there's no handling for when actual content becomes available, which might cause synchronization issues.
</summary>

```typescriptreact
const [doc, setDoc] = useState<string | undefined>(undefined)

const { editor } = useYamlEditor({ doc, setDoc })
```

</details>

</td></tr>
</table>

author_association: CONTRIBUTOR
user: login: qodo-free-for-open-source-projects[bot]
created_at: 2025-04-17T10:38:16Z
body: ## PR Code Suggestions ✨

<!-- a340034 -->

Explore these optional code suggestions:

<table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>Possible issue</td>
<td>



<details><summary>Add editor cleanup</summary>

___


**The current implementation doesn't handle cleanup when the component unmounts. <br>This can lead to memory leaks as the CodeMirror editor instance remains in <br>memory. Add a cleanup function to destroy the editor view when the component <br>unmounts.**

[frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx [127-139]](https://github.com/liam-hq/liam/pull/1385/files#diff-44353224589a1f306bb04ce18743a69316f3b45cc5972368a5f16653894abd90R127-R139)

```diff
 useEffect(() => {
   if (!view && container) {
     const state = EditorState.create({
       doc,
       extensions: [...extensions, updateListener],
     })
     const viewCurrent = new EditorView({
       state,
       parent: container,
     })
     setView(viewCurrent)
+    
+    return () => {
+      viewCurrent.destroy();
+    };
   }
 }, [view, doc, container, updateListener])
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=0 -->

<details><summary>Suggestion importance[1-10]: 8</summary>

__

Why: The suggestion addresses a potential memory leak by properly cleaning up the CodeMirror editor instance when the component unmounts. Without this cleanup, the editor instance would remain in memory even after the component is removed from the DOM, which is a significant resource management issue.

</details></details></td><td align=center>Medium

</td></tr><tr><td rowspan=1>Learned<br>best practice</td>
<td>



<details><summary>Add input validation to ensure required callback functions are valid before using them in hooks</summary>

___


**The <code>useYamlEditor</code> hook doesn't validate the <code>setDoc</code> function before using it in <br>the <code>updateListener</code>. If <code>setDoc</code> is undefined or not a function, it could cause <br>runtime errors when the document changes. Add validation at the beginning of the <br>hook to ensure <code>setDoc</code> is a valid function.**

[frontend/apps/app/features/schemas/pages/SchemaPage/components/OverrideEditor/useYamlEditor.tsx [108-119]](https://github.com/liam-hq/liam/pull/1385/files#diff-44353224589a1f306bb04ce18743a69316f3b45cc5972368a5f16653894abd90R108-R119)

```diff
 export const useYamlEditor = ({ doc, setDoc }: Props) => {
+  if (typeof setDoc !== 'function') {
+    throw new Error('setDoc must be a valid function');
+  }
+  
   const editor = useRef(null)
   const [container, setContainer] = useState<HTMLDivElement>()
   const [view, setView] = useState<EditorView>()
 
   const updateListener = useMemo(() => {
     return EditorView.updateListener.of((update: ViewUpdate) => {
       if (update.docChanged) {
         setDoc(update.state.doc.toString())
       }
     })
   }, [setDoc])
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 6</summary>

</details></details></td><td align=center>Low

</td></tr>
<tr><td align="center" colspan="2">

- [ ] More <!-- /improve --more_suggestions=true -->

</td><td></td></tr></tbody></table>


author_association: CONTRIBUTOR