AAMLabs (now Forge Labs) · Full-stack engineer

EDITe

A study-notebook marketplace with an editor that mixes rich text and canvas, paginated like A4 sheets, with PDF generation and Stripe Connect payments.

Client
AAMLabs (now Forge Labs)
Role
Full-stack engineer
Period
2025 to present

What it is

EDITe is a marketplace where students build study notebooks tied to public-exam notices, publish them as ebooks, and sell them to other candidates. The name changed from Caderno Edital to EDITe, but the idea holds: give a candidate a real authoring tool rather than a form, plus a place to resell what they produced.

The most interesting engineering work, and the hardest, was the editor. Almost everything else is a familiar marketplace; the editor is where the project turns into a real problem.

Dual-database architecture

The system runs on Next.js 16 (App Router) with two data sources, each for what it does well. PostgreSQL, through Prisma, holds what is relational and transactional: users, authentication, orders, payouts, and reviews. MongoDB, through Mongoose, holds the notebook content, which is a deeply nested tree (subjects → items → pages → sheets) and constantly changes shape as the user edits.

Using both was not a fad, it was a question of data shape. Forcing a free-form content tree into a relational schema, or forcing orders and payouts into a document, would push each thing into the wrong tool. Authentication is NextAuth v5 with Google, Facebook, and credentials, and files go to S3-compatible storage (MinIO).

The editor: structured rich text and free-form canvas

Each notebook sheet combines two things that usually do not coexist: a structured rich-text editor (TipTap) and a free-form drawing canvas (Fabric.js). The user writes organized prose and, alongside it, draws diagrams, annotates, and imports a PDF page to mark over.

The canvas is not a toy. It has a full tool system (pen, shapes, text, eraser, selection, and a hand to pan), its own history manager for undo and redo, and object interactivity control. The central challenge was making the two models coexist without one corrupting the other: when a notebook is reopened, the rich text has to come back as rich text and the canvas as editable Fabric objects, not as a flattened image.

The A4 page problem

The part that consumed me the most was treating the editor like real paper. Sheets have a fixed A4 size (794 by 1123 pixels at 96 DPI), and that creates a problem web editors usually ignore: what happens when text runs past the end of the page?

I first tried the elegant solution: detect the overflow in the DOM and redistribute the excess to the next sheet automatically, creating pages as needed. It worked, but it was an endless source of edge cases, because mutating the document while typing fights with the cursor, with autosave, and with the editor’s own history. After a long time on it, I switched to something more predictable: a page-limit block. When a sheet fills up, the plugin stops accepting input that adds content, while still allowing navigation, deletion, undo, and redo.

The lesson was hard and clear: automatically redistributing paginated rich text is a bottomless pit. An explicit, honest limit that the user understands immediately is worth more than magic that fails in subtle ways.

From canvas to PDF

Publishing a notebook as an ebook means turning all of it into a faithful PDF, and that requires capturing every sheet. Capturing a canvas at the right moment is half the battle. I learned the hard way that Fabric’s toDataURL does not always match what is on screen, that the capture has to be deferred to the next render frame, and that, when there is no client-generated thumbnail, you can render the Fabric JSON on the server with Puppeteer. The final PDF combines images of the canvas sheets with the converted rich text, and the published ebook reader renders the pages with pdf.js.

Payments and marketplace

Being multi-vendor, the money matters as much as the content. Each author is a seller with their own onboarding, and the platform keeps a fee. The first version used Asaas for the Brazilian side, but we migrated everything to Stripe Connect, with separate charges and transfers, which simplified having a single provider handle charging, payout, and withdrawal. There is also an escrow cycle: the author’s amount is held and released by a job, with a per-seller ledger recording every movement.

Status

In production and evolving, now under Forge Labs. The project started as Caderno Edital at AAMLabs and came along with the team’s migration. The editor is still the heart and the biggest challenge of the product: it is where every modeling decision shows up, because it mixes structured text, free-form drawing, and the stubborn constraint of a sheet of paper.

Stack

  • Next.js 16
  • React 19
  • Prisma 6
  • PostgreSQL
  • MongoDB
  • Mongoose 9
  • NextAuth v5
  • S3-compatible (MinIO)
  • Stripe Connect
  • TipTap 3
  • Fabric.js 6
  • TanStack Query
  • Puppeteer
  • @react-pdf/renderer
  • pdf.js