This document describes how to work with the docx-editor codebase as a set of focused agents (roles). It encodes architectural patterns, implicit contracts, and conventions that are not visible in README.md.
Use this document when you:
- Need a mental model: Understand how UI state, domain objects, and DOCX generation connect.
- Plan a change: Identify which files and layers are safe to touch.
- Coordinate agents: Keep human and AI contributors aligned on the same invariants.
- Framework: Next.js App Router (
app/), React 19, TypeScript, Tailwind CSS, shadcn-style UI. - Client boundary:
app/page.tsx(DocumentEditor) is a"use client"component.- All components under
components/and hooks underhooks/are client-side.
- Server boundary:
app/actions.tsexports the server actiongenerateDocumentwith"use server".- DOCX generation runs on the Node.js runtime and uses
BufferanddocxAPIs.
- Primary flow:
- User edits metadata and practicals in the left sidebar (
StudentForm,PracticalForm). - Center editor mutates local React state (
DocumentEditor). - Right pane renders a WYSIWYG-ish preview (
DocumentPreview). - On compile, the client serializes state into
FormDataand callsgenerateDocument. - Server builds a DOCX, returns a base64 string.
- Client reconstructs a
Bloband triggers a download.
- User edits metadata and practicals in the left sidebar (
Defined in app/types.ts and lib/factories.ts:
- StudentData:
name: stringrollNo: stringcourse: string
- Question:
id: string(ephemeral UI identifier)number: stringquestionText: stringcode: string
- Practical:
practicalNo: stringaim: stringquestions: Question[](at least one)outputs: File[](up to three from the UI)conclusion: string
- Factories (
lib/factories.ts):generateId():- Uses
crypto.randomUUID()when available, falls back toMath.random().toString(36). - IDs are not stable across reloads and are not suitable as database keys.
- Uses
createQuestion(number: string): Question:- Initializes an empty question with generated
id.
- Initializes an empty question with generated
createPractical(practicalNo: string): Practical:- Initializes a practical with one
Questionnumbered"1", no outputs, and empty text fields.
- Initializes a practical with one
- Design system:
- Tailwind-based theme defined in
tailwind.config.tsandapp/globals.css. - Shadcn-style primitives in
components/ui/:Button,Card,Input,Textarea,Label.
- Utility function
cninlib/utils.tsmerges Tailwind classes.
- Tailwind-based theme defined in
- Layout:
- Root layout (
app/layout.tsx) applies dark mode globally with Next.js fonts and Vercel analytics. DocumentEditor(app/page.tsx) composes:- A fixed
Header. - A three-pane desktop layout (sidebar editor, main form, preview).
- A hard-coded mobile restriction screen (
lg:hidden).
- A fixed
- Root layout (
- DOCX vs preview:
app/actions.tsandDocumentPreviewboth render:- Header: student name, roll number, course.
- Sections per practical:
PRACTICAL No.,AIM,Question N,Code,OUTPUT,CONCLUSION.
- Fonts:
- DOCX:
"Times New Roman"for text,"Courier New"for code. - Preview:
"Times New Roman"CSS stack and monospace CSS stack for code.
- DOCX:
layout.tsx:- Declares global fonts (
Space_Grotesk,JetBrains_Mono) and setsclassName="dark"on<html>. - Wraps
childrenand mounts@vercel/analytics. - Does not wrap with
ThemeProvider(seeapp/theme-provider.tsx).
- Declares global fonts (
page.tsx(DocumentEditor):- Owns all client-side state for:
StudentData.Practical[].activePracticalIndex.isGenerating.
- Wires:
- Forms (
StudentForm,PracticalForm). - Debounced state hooks (
useDebounced). - Preview (
DocumentPreview). - Compile action (
generateDocument).
- Forms (
- Encodes the
FormDataprotocol used bygenerateDocument.
- Owns all client-side state for:
actions.ts:- Server action
generateDocument(formData: FormData): Promise<string>:- Parses
StudentDataandPractical[]from flatFormDatakeys. - Builds a
Documentusing thedocxlibrary. - Uses
Packer.toBufferand encodes the result as base64.
- Parses
- Server action
theme-provider.tsx:- Thin wrapper over
next-themesThemeProvider. - Currently unused by
layout.tsx.
- Thin wrapper over
types.ts:- Defines the domain types used across the client and server.
- Global layout:
header.tsx:- Animated lab-style header with title, status indicator.
- Version badge (
v2.1.0) is hard-coded.
footer.tsx:- Social links (GitHub, Twitter, LinkedIn).
- Not currently rendered anywhere.
mobile-restricted.tsx:- Standalone mobile restriction screen.
- Not used by
DocumentEditor, which inlines a simpler mobile-only screen.
- Preview:
document-preview.tsx:- Memoized preview component.
- Accepts
studentData: StudentDataandpracticals: Practical[]. - Renders a pseudo-A4 page with styling approximating the DOCX output.
- Uses
useObjectUrlsto displayFileoutputs as images.
- Forms (
components/forms/):student-form.tsx(StudentForm):- Controlled inputs for
name,rollNo,course. - Delegates state updates via
onChangecallback.
- Controlled inputs for
practical-form.tsx(PracticalForm):- Controlled form for a single
Practical. - Delegates all mutations (
onPracticalChange,onQuestionChange, file handlers). - Guarantees at least one
Questionis present (canRemovegate). - Composes:
QuestionForm(per question).OutputGallery(foroutputs).
- Controlled form for a single
question-form.tsx(QuestionForm):- Controlled form for a single
Question. - Emits changes via
onQuestionChange(field, value).
- Controlled form for a single
output-gallery.tsx(OutputGallery):- Displays up to 3 images generated from
File[]. - Uses hidden
<input type="file">and a styledButtonas the trigger.
- Displays up to 3 images generated from
- UI primitives (
components/ui/):button.tsx:ButtonandbuttonVariantsdefined usingclass-variance-authority.- Encodes standard variants (
default,destructive,outline,secondary,ghost,link) and sizes.
card.tsx:Card,CardHeader,CardTitle,CardDescription,CardContent,CardFooter.
input.tsx:- Tailwind-styled monospace input with border, focus ring, and disabled states.
textarea.tsx:- Tailwind-styled monospace textarea with similar styling to
Input.
- Tailwind-styled monospace textarea with similar styling to
label.tsx:- Radix
Labelwrapped with acva-based style.
- Radix
field-label.tsx:- Standardized label that composes
Labelwith optional icon. - Used consistently across form components.
- Standardized label that composes
- Visual helpers:
grid-pattern.tsx:- Renders a CSS grid background overlay.
- Currently unused; preview uses the
.bg-grid-patternclass defined inglobals.cssinstead.
use-debounced.ts:useDebounced<T>(value: T, delay = 300): T:- Returns the value after a delay.
- Cancels pending timers on change or unmount.
- Used to debounce heavy preview updates.
use-object-urls.ts:useObjectUrls(files: File[]): string[]:- Maps
Fileobjects to object URLs viaURL.createObjectURL. - Registers cleanup to
URL.revokeObjectURLon unmount or whenfileschanges.
- Maps
utils.ts:cn(...inputs: ClassValue[]): string:- Wraps
clsxandtailwind-mergeto merge class names safely.
- Wraps
factories.ts:- See the Domain model section above.
tailwind.config.ts:- Dark mode via class.
- Content scanning for
pages/,components/, andapp/. - Extended theme:
- Custom fonts wired to CSS variables from
layout.tsx. - Brand colors (
background,primary,secondary,card, etc.). borderRadiustokens (lg,md,sm).borderColor.microfor subtle separators.backgroundImage.noiseusing/noise.svg.
- Custom fonts wired to CSS variables from
- Plugin:
tailwindcss-animate.
app/globals.css:- Base CSS variables for colors.
- Global
bodystyling with background, typography, andnoiseoverlay. - Custom scrollbar.
.glassand.bg-grid-patternutility classes.
next.config.mjs:experimental.turboenabled.- ESLint disabled during builds (
eslint.ignoreDuringBuilds: true). - Image domains configured for
localhostandavatars.githubusercontent.com.
components.json:- shadcn UI configuration:
- Style:
new-york. - Aliases:
@/components,@/lib,@/hooks,@/components/ui.
- Style:
- shadcn UI configuration:
DocumentEditor and generateDocument communicate via a stringly-typed FormData contract. These keys are API-level invariants:
- Student fields:
namerollNocourse
- Practicals:
- Index is zero-based:
pIndex = 0, 1, 2, .... - Loop terminates when
formData.has("practical_${pIndex}_no")is false.
- Index is zero-based:
- Per practical (index
pIndex):practical_${pIndex}_nopractical_${pIndex}_aimpractical_${pIndex}_conclusion
- Questions (index
qIndex, zero-based):- Loop terminates when
formData.has("practical_${pIndex}_question_${qIndex}_number")is false. - Keys:
practical_${pIndex}_question_${qIndex}_numberpractical_${pIndex}_question_${qIndex}_questionTextpractical_${pIndex}_question_${qIndex}_code
- Loop terminates when
- Outputs (index
oIndex, zero-based):- Loop terminates when
formData.has("practical_${pIndex}_output_${oIndex}")is false. - Key:
practical_${pIndex}_output_${oIndex}(value type:File)
- Loop terminates when
When you:
- Rename a field in
StudentForm,PracticalForm, orQuestionForm:- Ensure
nameandidattributes still match the expectedFormDatakeys or update both sides of the contract.
- Ensure
- Add a new field to the DOCX:
- Add it to the domain types.
- Add it to the client state and form components.
- Append it to
FormDatainDocumentEditor.handleGenerate. - Parse it in
generateDocumentand render it into theDocument.
- Practical constraints:
- There is always at least one practical in
practicals:- Initialization in
DocumentEditoris[createPractical("1")]. - UI prevents deleting the last practical (
canRemovechecks length).
- Initialization in
practicalNois user-editable and must stay unique enough for preview keys and user understanding.
- There is always at least one practical in
- Question constraints:
- Each practical always has at least one question:
createPracticalseedsquestionswith oneQuestion.QuestionFormonly allows removal ifquestions.length > 1.
Question.id:- Used as React
keyin lists and stable anchor for UI operations. - Never sent to the server; DOCX is keyed by
numberand order.
- Used as React
- Each practical always has at least one question:
- The UI enforces
outputs.length <= 3:OutputGallerydisplays a[current/3]counter.- New files are appended and then sliced to length 3.
- The server does not enforce this limit:
generateDocumentloops untilformData.has("practical_${pIndex}_output_${oIndex}")is false.- Server trusts the client to enforce the constraint.
useObjectUrlsassumes alloutputsentries areFileinstances:- Do not introduce non-
Filetypes intooutputswithout updating the hook.
- Do not introduce non-
- DOCX and preview share a structural contract:
- Header: name, roll number, course.
- Sections per practical:
- Title:
PRACTICAL No. X. AIM:.Question N:.- Question text.
Code:followed by block-level code.OUTPUT:followed by images.CONCLUSION:followed by text.
- Title:
- Changes to:
- Section order,
- Section labels,
- Formatting semantics (e.g., underlines, page breaks) must be reflected in both:
app/actions.ts(DOCX generation).components/document-preview.tsx(on-screen preview).
- Debouncing:
useDebouncedwrapsformDataandpracticalsbefore passing toDocumentPreview.- Contract: preview components should treat props as immutable snapshots and not mutate them.
- Memoization:
DocumentPreview,PracticalPreviewSection,OutputGallery,PracticalForm,QuestionForm, andStudentFormuseReact.memo.- Upstream callers must pass stable function references when possible (hence the use of
useCallbackinDocumentEditor).
generateDocumentusesBufferandFile:- This action must run on the Node.js runtime, not Edge (unless polyfilled).
generateIdusescrypto.randomUUID:- Called only in the browser (via
createQuestion/createPractical). - Avoid calling it in Node-only contexts unless
globalThis.cryptois available.
- Called only in the browser (via
This section defines recommended agent roles. You can assign them to people or tools.
Scope:
- Components under
components/andcomponents/ui/. - Layout and interaction patterns in
app/page.tsx.
Responsibilities:
- Maintain visual consistency using:
- Design tokens from
tailwind.config.ts. - Shared primitives (
Button,Card,Input,Textarea,FieldLabel).
- Design tokens from
- Keep the three-pane layout usable:
- Desktop-only viewport assumptions (
lg:flex,lg:hidden). - Keep forms and preview readable at common resolutions.
- Desktop-only viewport assumptions (
- Avoid introducing inline styles when a Tailwind class or design token exists.
Change entry points:
- Layout changes:
app/page.tsx,components/header.tsx,components/document-preview.tsx. - New controls: extend
components/forms/and reuse primitives undercomponents/ui/.
Scope:
app/types.tslib/factories.ts- Any domain-level validation or transformation you add later.
Responsibilities:
- Keep the domain model minimal and explicit.
- Ensure factories initialize values that are safe for both:
- Preview rendering.
- DOCX generation.
- When introducing new concepts (e.g., grading, metadata, timestamps):
- Add them to domain types.
- Provide factory defaults.
Change entry points:
- New domain field:
app/types.tsandlib/factories.ts. - Derived data: create separate pure functions (e.g.,
lib/derived.ts) rather than inlining logic in UI components.
Scope:
app/actions.ts- DOCX-specific formatting rules and structure.
Responsibilities:
- Maintain the
FormDataparsing logic and ensure it stays aligned with client state. - Preserve document layout invariants:
- Margins, fonts, alignment, underlines.
- Section ordering.
- Page break behavior.
- Keep performance acceptable:
- Avoid quadratic loops over practicals/questions.
- Avoid loading huge images without resizing or compression logic (future optimization).
Change entry points:
- New section in DOCX:
- Add parsing for the data in
generateDocument. - Insert new
ParagraphorImageRunsequences.
- Add parsing for the data in
- Formatting changes:
- Adjust
TextRunproperties (font, size, underline, bold). - Adjust margins and
PageBreakusage.
- Adjust
Scope:
- Hooks under
hooks/. - High-frequency rendering paths (
DocumentEditor,DocumentPreview, forms).
Responsibilities:
- Manage rendering cost by:
- Using
useDebouncedand memoization strategically. - Avoiding prop churn that invalidates memoization.
- Using
- Ensure file handling is safe and leak-free:
- Confirm
useObjectUrlsis used for any newFile-backed media.
- Confirm
Change entry points:
- Add new performance-sensitive flows:
- Prefer small, well-scoped hooks in
hooks/. - Keep them generic where possible.
- Prefer small, well-scoped hooks in
Scope:
tailwind.config.tsapp/globals.csscomponents/ui/*components.json
Responsibilities:
- Guard the global visual language:
- Colors, typography, spacing, radii.
- Ensure new UI components:
- Use design tokens instead of hard-coded colors or radii.
- Prefer composition over duplication.
Change entry points:
- New global utility class: add to
app/globals.cssunder@layer components. - New design token: extend
theme.extendintailwind.config.ts.
- Domain:
- Add the field to
StudentDatainapp/types.ts.
- Add the field to
- Factories:
- If it needs a default, extend factory functions or create a new helper.
- UI:
- Add a control for the field in
StudentForm. - Ensure
nameandidare set consistently.
- Add a control for the field in
- State:
- Extend
formDatastate inDocumentEditor. - Update
handleChangeto include the new field (it already spreads byname, so new fields should work automatically ifnamematches).
- Extend
- FormData:
- Ensure
handleGenerateappends the new field toFormDataif you diverge from the default key approach.
- Ensure
- Server:
- Parse the field in
generateDocument. - Render it in the desired location using
ParagraphandTextRun.
- Parse the field in
- Preview:
- Reflect the new field visually in
DocumentPreview.
- Reflect the new field visually in
- Update labels and ordering in
DocumentPreview. - Mirror those changes in
app/actions.ts:- Keep section order and labeling aligned.
- Verify:
- Preview looks correct for multiple practicals.
- Generated DOCX reflects the same structure.
- Update
OutputGallery:- Change the limit constant (
3) in:- Counter display.
- Slicing logic when pushing new files.
- Change the limit constant (
- Optionally add server-side enforcement:
- In
generateDocument, ignore outputs after the desired maximum.
- In
- Verify:
- More than the new max cannot be added from the UI.
- DOCX renders the expected number of images.
- Create dedicated validation utilities (e.g.,
lib/validation.ts). - Use them in:
DocumentEditorbefore callinggenerateDocument.- Optionally, in
generateDocumentto guard against malformedFormData.
- Surface validation errors in the UI using:
- Inline messages near fields.
- A toast or banner pattern if introduced later.
These elements reflect previous iterations and implicit decisions. Treat them as technical debt or extension points, not authoritative patterns.
components/mobile-restricted.tsx:- Full-screen mobile restriction view.
- Currently unused;
DocumentEditorimplements its own simpler mobile screen. - Before reusing, decide whether to:
- Consolidate on this component, or
- Remove it to reduce confusion.
components/footer.tsx:- Not mounted in
layout.tsxorpage.tsx. - Decide whether the product deliberately omits a footer:
- If yes, delete or document as deprecated.
- If no, mount in
layout.tsxorDocumentEditor.
- Not mounted in
components/grid-pattern.tsx:- Overlaps with
.bg-grid-patternin CSS. - Prefer one approach:
- Use the component where appropriate, or
- Remove it if the CSS class is the standard.
- Overlaps with
app/theme-provider.tsx:- Provides theme switching via
next-themes. - Currently unused; dark theme is hard-coded via
className="dark". - If you plan to support multiple themes:
- Wrap
childreninThemeProviderinlayout.tsx. - Update Tailwind and CSS to support variants.
- Wrap
- Provides theme switching via
Use this section to quickly build a mental model and ship an initial, low-risk change.
- Skim:
app/page.tsx(overall flow).app/actions.ts(DOCX generation).components/forms/*(editing model).components/document-preview.tsx(preview).
- Confirm you understand:
StudentData,Practical,Question.- How
FormDatais built and parsed.
- Identify:
- How
FieldLabelandButtonare used. - Where memoization and
useDebouncedare applied. - Where
outputsare handled end-to-end.
- How
- Note:
- Preview–export alignment constraints.
- UI-only vs server-only code paths.
- Add or refine a small UI element:
- Example: improve labels or placeholders in
StudentFormorQuestionForm.
- Example: improve labels or placeholders in
- Improve preview–export parity:
- Example: adjust styling or text to better match the DOCX structure.
- Tighten an invariant:
- Example: centralize validation in a new helper and use it before calling
generateDocument.
- Example: centralize validation in a new helper and use it before calling
- Resolve a legacy ghost:
- Example: delete or integrate
Footer,GridPattern, orMobileRestrictedafter making a clear decision.
- Example: delete or integrate
Each of these changes should:
- Touch a small, well-defined surface area.
- Respect the contracts listed in this document.
- Be straightforward to test manually in the app.