Vibecode Adobe Express
track this build4 phases, 12 steps, beginner friendly0%You can build a quick image editor and template exporter, but Adobe's stock/licensing, PDF/media tooling, brand assets, and Creative Cloud tie-ins are the product.
You are building a lean indie version of Adobe Express. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Adobe Express · indie build A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | A headless browser screenshotting an HTML template at an exact pixel size | the layout engine you already know beats any drawing API for text and boxes | | Templates | Plain HTML and CSS files reading values from one brand file | a template you can open in a browser is a template you can debug | | Brand | One file holding fonts, colours, logo and defaults, read by every template | changing the brand should change every graphic, which is the entire point | | Cut-outs | A local background removal model, chosen deliberately and recorded | the default model in the popular library is not free for commercial use | | Provenance | A sidecar file per export listing fields, template version and asset licences | you will be asked what is in a graphic, and the answer should not be a memory | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the renderer and the batch command. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **A headless browser automation library with a bundled browser** · free - Why: The render is a screenshot of a real page, which is how you get text layout that is correct rather than approximated. - Get it: Install the library and let it download its browser. - Verify: A blank page screenshots at an exact requested size - [ ] **A local background removal library and a deliberately chosen model** · free for the permissive models; a commercial agreement for some others - Why: The library is permissive but its models are not uniformly so, and the default one requires a paid agreement for commercial use. - Get it: Install the library, read its model list and its licence notes, then choose a model and record why. - Verify: The chosen model name and its licence are in configuration before any cut-out is produced - [ ] **Fonts you have the right to use, with their licence files** · free for open fonts; whatever the foundry charges otherwise - Why: Rendering is unrestricted under an open font licence but the file itself travels with conditions, and a client font may be neither. - Get it: Download the font files with their licence text and keep both in the repository. - Verify: Every font in the brand file has a licence recorded in the assets table - [ ] **Your output sizes, with the date you checked them** · free - Why: Platform image specifications change and a size baked into a template is never revisited, so the date is part of the data. - Get it: Look up the current published specification for each platform you export to and record the dimensions with the date. - Verify: Every size in configuration carries a checked date ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. - Adobe Stock/assets - PDF/video tooling - brand kits - Creative Cloud integration - mobile apps - licensing coverage If one of those is essential to you, that is the reason to keep paying for Adobe Express, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Adobe Express The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a template renderer that turns a brand file and a handful of fields into exact-size graphics you can re-render later to replace Adobe Express. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An export tool is a licensing machine, and the licence questions are not theoretical. The background removal library most people reach for is itself permissively licensed, but its own documentation says the model weights carry their own licences and tells you to check before using any model commercially, and its default model is released for non-commercial use with commercial use requiring a paid agreement. Two of the alternatives it offers are cleanly permissive, and at least one sits on a training dataset whose terms forbid commercial use, which makes the weights unclear rather than safe. So the model is a recorded choice, not a default you inherited. Fonts work the other way round and people get this backwards. Under the Open Font Licence, rendering text into a graphic is unrestricted and the resulting artwork is yours; it is only redistributing, bundling or modifying the font file itself that brings conditions, and the licence is explicit that font software may not be sold by itself and that a modified version may not keep the reserved font name. So rendering into your exports is fine, and shipping the font files with your project needs the notice to travel with them. Therefore: every font, image and model in the project has a recorded licence and source, the manifest is written by the build rather than by hand, and an export tells you what went into it. ### Stack (fixed, do not substitute) - Rendering: A headless browser screenshotting an HTML template at an exact pixel size · the layout engine you already know beats any drawing API for text and boxes - Templates: Plain HTML and CSS files reading values from one brand file · a template you can open in a browser is a template you can debug - Brand: One file holding fonts, colours, logo and defaults, read by every template · changing the brand should change every graphic, which is the entire point - Cut-outs: A local background removal model, chosen deliberately and recorded · the default model in the popular library is not free for commercial use - Provenance: A sidecar file per export listing fields, template version and asset licences · you will be asked what is in a graphic, and the answer should not be a memory ### Data model (create this before Phase 1) - templates(id, name, width, height, version, html_path, fields_json) · size is part of the identity, not a setting - brand(fonts_json, colours_json, logo_path, licence_notes) · one record, read by every render - assets(id, kind, path, sha256, source, licence_name, licence_url, commercial_ok, added_at) · kind is font, image or model - renders(id, template_id, template_version, field_values_json, asset_ids_json, output_path, rendered_at, duration_ms) - sidecars(render_id, json_path) · the file written beside every export so it can be reproduced - The assets table is the one that stops this being a tool that quietly puts a non-commercial model's output on a billboard. ### Phase 1 · The same pixels every time Build: Load the template in a headless browser at the exact width and height with a fixed device pixel ratio. Wait for fonts to finish loading and for every image to decode before the screenshot. Write the file, then compare two renders byte for byte in a test. Trap: Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. Done when: Two renders of one template with identical values are byte-identical, tested twenty times in a row. A render with a slow-loading font never produces fallback text, tested by throttling the font request. Output dimensions match the template exactly at the configured pixel ratio, checked by reading the file header. A missing font or image fails the render with a named error instead of producing a silently wrong graphic. Do not build yet: No brand file, text fitting, cut-outs or batch export yet. ### Phase 2 · One brand file that every template reads Build: Put fonts, colours, logo and spacing defaults in one file, exposed to templates as custom properties. No template hard-codes a colour or a font family. Re-render every past export from its sidecar after a brand change and show the differences. Trap: Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. Done when: A template declaring a literal colour or font family fails the build, tested with a deliberate one. Changing a brand colour and re-rendering changes every affected graphic, verified by comparing outputs. The brand file records which font files are used and where they came from. Re-rendering a past export from its sidecar after a brand change succeeds and produces a visibly updated file. Do not build yet: No text fitting, cut-outs or batch export yet. ### Phase 3 · Real text in a fixed box Build: Fit text by measuring it in the page and reducing the size within a declared range, rather than by guessing from character count. Declare per field a minimum size, a maximum number of lines, and what happens when it still does not fit. Test with a name containing emoji, a long compound word, and a right-to-left string. Trap: Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. Done when: A headline three times the expected length either fits within the declared line limit or fails with the field named, never overflows, tested with both. Emoji and a long unbreakable word are both handled without overflow, tested. Text never renders below the declared minimum size. A right-to-left string renders in the correct direction with punctuation in the right place. Do not build yet: No cut-outs, batch export or provenance yet. ### Phase 4 · Cut-outs, and the licence that comes with them Build: Run background removal locally with the model named in configuration, never the library default. Record the model, its licence name, its licence URL and whether commercial use is permitted in the assets table. Handle the edge quality problem by offering a matte adjustment, and fall back to using the original image when removal fails rather than exporting a mangled cut-out. Trap: Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. Done when: The model is named in configuration and no code path uses a library default, verified by review. Every render using a cut-out records the model and its licence in the sidecar. A project marked commercial refuses to run a model recorded as non-commercial, tested. A failed removal leaves the original image in place and marks the render, rather than exporting a partial cut-out. Do not build yet: No batch export or platform sizes yet. ### Phase 5 · Batches, sizes and a graphic you can rebuild Build: Render one set of field values across several template sizes in a batch, writing a sidecar for each. Take the target dimensions from a configuration file with the date you checked each platform's published specification, because those change. Name outputs predictably, write in the standard colour space, and provide a command that rebuilds a graphic from its sidecar. Trap: Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. Done when: One batch renders the same values across every configured size and writes a sidecar per output, tested. Rebuilding from a sidecar reproduces a byte-identical file when the brand and template version are unchanged, tested. Sizes carry the date the platform specification was last checked and the batch prints it. Exports are written in the standard colour space and open with correct colours in another application, checked once by eye. Do not build yet: Nothing after this. ### Out of scope (and why) - Stock photography and premium templates, which are the licensed library you are paying for and cannot be reproduced by writing code. - Video and PDF editing, which are separate pipelines with their own failure modes and do not share this one's design. - Generative image creation, which brings a rented model, a cost per image and a rights question per output. - A drag and drop canvas, since the value here is that a template renders the same way every time and a canvas is the opposite of that. ### README must contain - That the background removal model is named in configuration, why the library default is not used, and where its licence is recorded - That rendering a font into an image is unrestricted under the Open Font Licence while redistributing the font file carries conditions, and which of the two this project does - That every export writes a sidecar and how to rebuild a graphic from one - Which platform sizes are configured and the date each was checked against the platform's published specification - That renders are deterministic, and the test that proves it ===== AGENTS.md ===== # Agent instructions · Adobe Express indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser screenshotting an HTML template at an exact pixel size, Plain HTML and CSS files reading values from one brand file, One file holding fonts, colours, logo and defaults, read by every template, A local background removal model, chosen deliberately and recorded, A sidecar file per export listing fields, template version and asset licences. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ===== BUILD_PLAN.md ===== # Build plan · Adobe Express A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The same pixels every time Rendering a template twice with the same values produces identical files. ### Steps 1. Wait for fonts and images before the shot The load event is not the same as ready to photograph. Files: `src/render.js`, `tests/determinism.test.js` 2. Fix the pixel ratio and the viewport Size is part of the template identity. Files: `src/render.js` 3. Fail loudly on a missing asset A silently wrong graphic is worse than no graphic. Files: `src/assets.js` ### Done when - [ ] Twenty consecutive renders are byte-identical - [ ] A throttled font never yields fallback text - [ ] Output dimensions match the template exactly - [ ] A missing asset fails with a named error ### Watch out - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. ## Phase 2 · One brand file that every template reads Changing a colour or a font once changes every graphic on the next render. ### Steps 1. Expose the brand as custom properties One source for colour and type, read by every template. Files: `src/brand.js`, `templates/_base.css` 2. Fail the build on a hard-coded colour Drift starts with one exception nobody removed. Files: `tests/no-literals.test.js` 3. Re-render from sidecars after a brand change The old graphics are the ones that look wrong first. Files: `src/rerender.js` ### Done when - [ ] A hard-coded colour or font fails the build - [ ] A brand change reaches every template - [ ] Font sources are recorded in the brand file - [ ] Past exports re-render from their sidecars ### Watch out - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. ## Phase 3 · Real text in a fixed box A long headline and a short one both look deliberate. ### Steps 1. Measure, then fit within a declared range Guessing from character count breaks on the first unusual string. Files: `src/fit.js`, `tests/fit.test.js` 2. Declare a minimum size and a line limit per field Unreadably small is a failure, not a solution. Files: `templates/schema.json` 3. Test with emoji, long words and right-to-left text All three appear in real names. Files: `tests/text-cases.test.js` ### Done when - [ ] An over-long headline fits or fails by name - [ ] Emoji and unbreakable words do not overflow - [ ] Text never goes below the minimum size - [ ] Right-to-left strings render correctly ### Watch out - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. ## Phase 4 · Cut-outs, and the licence that comes with them Background removal runs locally with a model you chose on purpose and recorded. ### Steps 1. Name the model, never take the default The default is the one with the commercial restriction. Files: `config/models.json`, `src/cutout.js` 2. Record the licence beside every output You will be asked, possibly by a client's lawyer. Files: `src/assets.js`, `src/sidecar.js` 3. Refuse a non-commercial model on a commercial project Better a failed render than a licence problem. Files: `src/cutout.js`, `tests/licence.test.js` ### Done when - [ ] No library default model is ever used - [ ] The model licence appears in every sidecar - [ ] A commercial project refuses a non-commercial model - [ ] A failed removal falls back rather than mangles ### Watch out - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ## Not in this build - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. ## After v1, if you want it - A contact sheet of every template rendered with sample values, which catches brand drift at a glance - A second brand file for a client, since everything already reads from one place - Animated exports of the same templates, once the still renders have been stable for a month ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. HTML and CSS templates. Each declares its own size. TEMPLATE_DIR=./templates # Required. Fonts, colours and logo. The only place these are defined. BRAND_FILE=./brand.json # Required. Exports and their sidecars, named predictably by date and template. OUTPUT_DIR=~/Graphics # Required. Named explicitly. The library default is not permitted here. CUTOUT_MODEL=a permissively licensed segmentation model name # Required. Commercial or personal. A commercial project refuses non-commercial model weights. PROJECT_USE=commercial # Required. Fixed, because it is part of what makes two renders identical. DEVICE_PIXEL_RATIO=2
You are building a lean indie version of Adobe Express. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Adobe Express · indie build A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | A headless browser screenshotting an HTML template at an exact pixel size | the layout engine you already know beats any drawing API for text and boxes | | Templates | Plain HTML and CSS files reading values from one brand file | a template you can open in a browser is a template you can debug | | Brand | One file holding fonts, colours, logo and defaults, read by every template | changing the brand should change every graphic, which is the entire point | | Cut-outs | A local background removal model, chosen deliberately and recorded | the default model in the popular library is not free for commercial use | | Provenance | A sidecar file per export listing fields, template version and asset licences | you will be asked what is in a graphic, and the answer should not be a memory | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the renderer and the batch command. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **A headless browser automation library with a bundled browser** · free - Why: The render is a screenshot of a real page, which is how you get text layout that is correct rather than approximated. - Get it: Install the library and let it download its browser. - Verify: A blank page screenshots at an exact requested size - [ ] **A local background removal library and a deliberately chosen model** · free for the permissive models; a commercial agreement for some others - Why: The library is permissive but its models are not uniformly so, and the default one requires a paid agreement for commercial use. - Get it: Install the library, read its model list and its licence notes, then choose a model and record why. - Verify: The chosen model name and its licence are in configuration before any cut-out is produced - [ ] **Fonts you have the right to use, with their licence files** · free for open fonts; whatever the foundry charges otherwise - Why: Rendering is unrestricted under an open font licence but the file itself travels with conditions, and a client font may be neither. - Get it: Download the font files with their licence text and keep both in the repository. - Verify: Every font in the brand file has a licence recorded in the assets table - [ ] **Your output sizes, with the date you checked them** · free - Why: Platform image specifications change and a size baked into a template is never revisited, so the date is part of the data. - Get it: Look up the current published specification for each platform you export to and record the dimensions with the date. - Verify: Every size in configuration carries a checked date ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. - Adobe Stock/assets - PDF/video tooling - brand kits - Creative Cloud integration - mobile apps - licensing coverage If one of those is essential to you, that is the reason to keep paying for Adobe Express, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Adobe Express The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a template renderer that turns a brand file and a handful of fields into exact-size graphics you can re-render later to replace Adobe Express. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An export tool is a licensing machine, and the licence questions are not theoretical. The background removal library most people reach for is itself permissively licensed, but its own documentation says the model weights carry their own licences and tells you to check before using any model commercially, and its default model is released for non-commercial use with commercial use requiring a paid agreement. Two of the alternatives it offers are cleanly permissive, and at least one sits on a training dataset whose terms forbid commercial use, which makes the weights unclear rather than safe. So the model is a recorded choice, not a default you inherited. Fonts work the other way round and people get this backwards. Under the Open Font Licence, rendering text into a graphic is unrestricted and the resulting artwork is yours; it is only redistributing, bundling or modifying the font file itself that brings conditions, and the licence is explicit that font software may not be sold by itself and that a modified version may not keep the reserved font name. So rendering into your exports is fine, and shipping the font files with your project needs the notice to travel with them. Therefore: every font, image and model in the project has a recorded licence and source, the manifest is written by the build rather than by hand, and an export tells you what went into it. ### Stack (fixed, do not substitute) - Rendering: A headless browser screenshotting an HTML template at an exact pixel size · the layout engine you already know beats any drawing API for text and boxes - Templates: Plain HTML and CSS files reading values from one brand file · a template you can open in a browser is a template you can debug - Brand: One file holding fonts, colours, logo and defaults, read by every template · changing the brand should change every graphic, which is the entire point - Cut-outs: A local background removal model, chosen deliberately and recorded · the default model in the popular library is not free for commercial use - Provenance: A sidecar file per export listing fields, template version and asset licences · you will be asked what is in a graphic, and the answer should not be a memory ### Data model (create this before Phase 1) - templates(id, name, width, height, version, html_path, fields_json) · size is part of the identity, not a setting - brand(fonts_json, colours_json, logo_path, licence_notes) · one record, read by every render - assets(id, kind, path, sha256, source, licence_name, licence_url, commercial_ok, added_at) · kind is font, image or model - renders(id, template_id, template_version, field_values_json, asset_ids_json, output_path, rendered_at, duration_ms) - sidecars(render_id, json_path) · the file written beside every export so it can be reproduced - The assets table is the one that stops this being a tool that quietly puts a non-commercial model's output on a billboard. ### Phase 1 · The same pixels every time Build: Load the template in a headless browser at the exact width and height with a fixed device pixel ratio. Wait for fonts to finish loading and for every image to decode before the screenshot. Write the file, then compare two renders byte for byte in a test. Trap: Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. Done when: Two renders of one template with identical values are byte-identical, tested twenty times in a row. A render with a slow-loading font never produces fallback text, tested by throttling the font request. Output dimensions match the template exactly at the configured pixel ratio, checked by reading the file header. A missing font or image fails the render with a named error instead of producing a silently wrong graphic. Do not build yet: No brand file, text fitting, cut-outs or batch export yet. ### Phase 2 · One brand file that every template reads Build: Put fonts, colours, logo and spacing defaults in one file, exposed to templates as custom properties. No template hard-codes a colour or a font family. Re-render every past export from its sidecar after a brand change and show the differences. Trap: Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. Done when: A template declaring a literal colour or font family fails the build, tested with a deliberate one. Changing a brand colour and re-rendering changes every affected graphic, verified by comparing outputs. The brand file records which font files are used and where they came from. Re-rendering a past export from its sidecar after a brand change succeeds and produces a visibly updated file. Do not build yet: No text fitting, cut-outs or batch export yet. ### Phase 3 · Real text in a fixed box Build: Fit text by measuring it in the page and reducing the size within a declared range, rather than by guessing from character count. Declare per field a minimum size, a maximum number of lines, and what happens when it still does not fit. Test with a name containing emoji, a long compound word, and a right-to-left string. Trap: Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. Done when: A headline three times the expected length either fits within the declared line limit or fails with the field named, never overflows, tested with both. Emoji and a long unbreakable word are both handled without overflow, tested. Text never renders below the declared minimum size. A right-to-left string renders in the correct direction with punctuation in the right place. Do not build yet: No cut-outs, batch export or provenance yet. ### Phase 4 · Cut-outs, and the licence that comes with them Build: Run background removal locally with the model named in configuration, never the library default. Record the model, its licence name, its licence URL and whether commercial use is permitted in the assets table. Handle the edge quality problem by offering a matte adjustment, and fall back to using the original image when removal fails rather than exporting a mangled cut-out. Trap: Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. Done when: The model is named in configuration and no code path uses a library default, verified by review. Every render using a cut-out records the model and its licence in the sidecar. A project marked commercial refuses to run a model recorded as non-commercial, tested. A failed removal leaves the original image in place and marks the render, rather than exporting a partial cut-out. Do not build yet: No batch export or platform sizes yet. ### Phase 5 · Batches, sizes and a graphic you can rebuild Build: Render one set of field values across several template sizes in a batch, writing a sidecar for each. Take the target dimensions from a configuration file with the date you checked each platform's published specification, because those change. Name outputs predictably, write in the standard colour space, and provide a command that rebuilds a graphic from its sidecar. Trap: Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. Done when: One batch renders the same values across every configured size and writes a sidecar per output, tested. Rebuilding from a sidecar reproduces a byte-identical file when the brand and template version are unchanged, tested. Sizes carry the date the platform specification was last checked and the batch prints it. Exports are written in the standard colour space and open with correct colours in another application, checked once by eye. Do not build yet: Nothing after this. ### Out of scope (and why) - Stock photography and premium templates, which are the licensed library you are paying for and cannot be reproduced by writing code. - Video and PDF editing, which are separate pipelines with their own failure modes and do not share this one's design. - Generative image creation, which brings a rented model, a cost per image and a rights question per output. - A drag and drop canvas, since the value here is that a template renders the same way every time and a canvas is the opposite of that. ### README must contain - That the background removal model is named in configuration, why the library default is not used, and where its licence is recorded - That rendering a font into an image is unrestricted under the Open Font Licence while redistributing the font file carries conditions, and which of the two this project does - That every export writes a sidecar and how to rebuild a graphic from one - Which platform sizes are configured and the date each was checked against the platform's published specification - That renders are deterministic, and the test that proves it ===== AGENTS.md ===== # Agent instructions · Adobe Express indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser screenshotting an HTML template at an exact pixel size, Plain HTML and CSS files reading values from one brand file, One file holding fonts, colours, logo and defaults, read by every template, A local background removal model, chosen deliberately and recorded, A sidecar file per export listing fields, template version and asset licences. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ===== BUILD_PLAN.md ===== # Build plan · Adobe Express A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The same pixels every time Rendering a template twice with the same values produces identical files. ### Steps 1. Wait for fonts and images before the shot The load event is not the same as ready to photograph. Files: `src/render.js`, `tests/determinism.test.js` 2. Fix the pixel ratio and the viewport Size is part of the template identity. Files: `src/render.js` 3. Fail loudly on a missing asset A silently wrong graphic is worse than no graphic. Files: `src/assets.js` ### Done when - [ ] Twenty consecutive renders are byte-identical - [ ] A throttled font never yields fallback text - [ ] Output dimensions match the template exactly - [ ] A missing asset fails with a named error ### Watch out - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. ## Phase 2 · One brand file that every template reads Changing a colour or a font once changes every graphic on the next render. ### Steps 1. Expose the brand as custom properties One source for colour and type, read by every template. Files: `src/brand.js`, `templates/_base.css` 2. Fail the build on a hard-coded colour Drift starts with one exception nobody removed. Files: `tests/no-literals.test.js` 3. Re-render from sidecars after a brand change The old graphics are the ones that look wrong first. Files: `src/rerender.js` ### Done when - [ ] A hard-coded colour or font fails the build - [ ] A brand change reaches every template - [ ] Font sources are recorded in the brand file - [ ] Past exports re-render from their sidecars ### Watch out - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. ## Phase 3 · Real text in a fixed box A long headline and a short one both look deliberate. ### Steps 1. Measure, then fit within a declared range Guessing from character count breaks on the first unusual string. Files: `src/fit.js`, `tests/fit.test.js` 2. Declare a minimum size and a line limit per field Unreadably small is a failure, not a solution. Files: `templates/schema.json` 3. Test with emoji, long words and right-to-left text All three appear in real names. Files: `tests/text-cases.test.js` ### Done when - [ ] An over-long headline fits or fails by name - [ ] Emoji and unbreakable words do not overflow - [ ] Text never goes below the minimum size - [ ] Right-to-left strings render correctly ### Watch out - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. ## Phase 4 · Cut-outs, and the licence that comes with them Background removal runs locally with a model you chose on purpose and recorded. ### Steps 1. Name the model, never take the default The default is the one with the commercial restriction. Files: `config/models.json`, `src/cutout.js` 2. Record the licence beside every output You will be asked, possibly by a client's lawyer. Files: `src/assets.js`, `src/sidecar.js` 3. Refuse a non-commercial model on a commercial project Better a failed render than a licence problem. Files: `src/cutout.js`, `tests/licence.test.js` ### Done when - [ ] No library default model is ever used - [ ] The model licence appears in every sidecar - [ ] A commercial project refuses a non-commercial model - [ ] A failed removal falls back rather than mangles ### Watch out - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ## Not in this build - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. ## After v1, if you want it - A contact sheet of every template rendered with sample values, which catches brand drift at a glance - A second brand file for a client, since everything already reads from one place - Animated exports of the same templates, once the still renders have been stable for a month ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. HTML and CSS templates. Each declares its own size. TEMPLATE_DIR=./templates # Required. Fonts, colours and logo. The only place these are defined. BRAND_FILE=./brand.json # Required. Exports and their sidecars, named predictably by date and template. OUTPUT_DIR=~/Graphics # Required. Named explicitly. The library default is not permitted here. CUTOUT_MODEL=a permissively licensed segmentation model name # Required. Commercial or personal. A commercial project refuses non-commercial model weights. PROJECT_USE=commercial # Required. Fixed, because it is part of what makes two renders identical. DEVICE_PIXEL_RATIO=2
You are building a production product version of Adobe Express. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Adobe Express · product brief ## Problem You can build a quick image editor and template exporter, but Adobe's stock/licensing, PDF/media tooling, brand assets, and Creative Cloud tie-ins are the product. ## Product outcome A renderer that produces the same pixels every time, fits real text without overflowing, keeps a licence record for every asset that reaches an export, and can rebuild any past graphic from the file written beside it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - canvas/video editor - media processing - image AI API - template assets - storage ## Explicit non-goals for v1 - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. - Adobe Stock/assets - PDF/video tooling - brand kits - Creative Cloud integration - mobile apps - licensing coverage ## Success criteria - Twenty consecutive renders are confirmed byte-identical - A hard-coded colour is confirmed to fail the build - An over-long headline is confirmed to fit or fail by name - A commercial project is confirmed to refuse non-commercial weights - A rebuild from a sidecar is confirmed byte-identical ===== BRIEF.md ===== # Build brief · Adobe Express The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a template renderer that turns a brand file and a handful of fields into exact-size graphics you can re-render later to replace Adobe Express. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An export tool is a licensing machine, and the licence questions are not theoretical. The background removal library most people reach for is itself permissively licensed, but its own documentation says the model weights carry their own licences and tells you to check before using any model commercially, and its default model is released for non-commercial use with commercial use requiring a paid agreement. Two of the alternatives it offers are cleanly permissive, and at least one sits on a training dataset whose terms forbid commercial use, which makes the weights unclear rather than safe. So the model is a recorded choice, not a default you inherited. Fonts work the other way round and people get this backwards. Under the Open Font Licence, rendering text into a graphic is unrestricted and the resulting artwork is yours; it is only redistributing, bundling or modifying the font file itself that brings conditions, and the licence is explicit that font software may not be sold by itself and that a modified version may not keep the reserved font name. So rendering into your exports is fine, and shipping the font files with your project needs the notice to travel with them. Therefore: every font, image and model in the project has a recorded licence and source, the manifest is written by the build rather than by hand, and an export tells you what went into it. ### Stack (fixed, do not substitute) - Rendering: A headless browser screenshotting an HTML template at an exact pixel size · the layout engine you already know beats any drawing API for text and boxes - Templates: Plain HTML and CSS files reading values from one brand file · a template you can open in a browser is a template you can debug - Brand: One file holding fonts, colours, logo and defaults, read by every template · changing the brand should change every graphic, which is the entire point - Cut-outs: A local background removal model, chosen deliberately and recorded · the default model in the popular library is not free for commercial use - Provenance: A sidecar file per export listing fields, template version and asset licences · you will be asked what is in a graphic, and the answer should not be a memory ### Data model (create this before Phase 1) - templates(id, name, width, height, version, html_path, fields_json) · size is part of the identity, not a setting - brand(fonts_json, colours_json, logo_path, licence_notes) · one record, read by every render - assets(id, kind, path, sha256, source, licence_name, licence_url, commercial_ok, added_at) · kind is font, image or model - renders(id, template_id, template_version, field_values_json, asset_ids_json, output_path, rendered_at, duration_ms) - sidecars(render_id, json_path) · the file written beside every export so it can be reproduced - The assets table is the one that stops this being a tool that quietly puts a non-commercial model's output on a billboard. ### Phase 1 · The same pixels every time Build: Load the template in a headless browser at the exact width and height with a fixed device pixel ratio. Wait for fonts to finish loading and for every image to decode before the screenshot. Write the file, then compare two renders byte for byte in a test. Trap: Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. Done when: Two renders of one template with identical values are byte-identical, tested twenty times in a row. A render with a slow-loading font never produces fallback text, tested by throttling the font request. Output dimensions match the template exactly at the configured pixel ratio, checked by reading the file header. A missing font or image fails the render with a named error instead of producing a silently wrong graphic. Do not build yet: No brand file, text fitting, cut-outs or batch export yet. ### Phase 2 · One brand file that every template reads Build: Put fonts, colours, logo and spacing defaults in one file, exposed to templates as custom properties. No template hard-codes a colour or a font family. Re-render every past export from its sidecar after a brand change and show the differences. Trap: Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. Done when: A template declaring a literal colour or font family fails the build, tested with a deliberate one. Changing a brand colour and re-rendering changes every affected graphic, verified by comparing outputs. The brand file records which font files are used and where they came from. Re-rendering a past export from its sidecar after a brand change succeeds and produces a visibly updated file. Do not build yet: No text fitting, cut-outs or batch export yet. ### Phase 3 · Real text in a fixed box Build: Fit text by measuring it in the page and reducing the size within a declared range, rather than by guessing from character count. Declare per field a minimum size, a maximum number of lines, and what happens when it still does not fit. Test with a name containing emoji, a long compound word, and a right-to-left string. Trap: Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. Done when: A headline three times the expected length either fits within the declared line limit or fails with the field named, never overflows, tested with both. Emoji and a long unbreakable word are both handled without overflow, tested. Text never renders below the declared minimum size. A right-to-left string renders in the correct direction with punctuation in the right place. Do not build yet: No cut-outs, batch export or provenance yet. ### Phase 4 · Cut-outs, and the licence that comes with them Build: Run background removal locally with the model named in configuration, never the library default. Record the model, its licence name, its licence URL and whether commercial use is permitted in the assets table. Handle the edge quality problem by offering a matte adjustment, and fall back to using the original image when removal fails rather than exporting a mangled cut-out. Trap: Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. Done when: The model is named in configuration and no code path uses a library default, verified by review. Every render using a cut-out records the model and its licence in the sidecar. A project marked commercial refuses to run a model recorded as non-commercial, tested. A failed removal leaves the original image in place and marks the render, rather than exporting a partial cut-out. Do not build yet: No batch export or platform sizes yet. ### Phase 5 · Batches, sizes and a graphic you can rebuild Build: Render one set of field values across several template sizes in a batch, writing a sidecar for each. Take the target dimensions from a configuration file with the date you checked each platform's published specification, because those change. Name outputs predictably, write in the standard colour space, and provide a command that rebuilds a graphic from its sidecar. Trap: Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. Done when: One batch renders the same values across every configured size and writes a sidecar per output, tested. Rebuilding from a sidecar reproduces a byte-identical file when the brand and template version are unchanged, tested. Sizes carry the date the platform specification was last checked and the batch prints it. Exports are written in the standard colour space and open with correct colours in another application, checked once by eye. Do not build yet: Nothing after this. ### Out of scope (and why) - Stock photography and premium templates, which are the licensed library you are paying for and cannot be reproduced by writing code. - Video and PDF editing, which are separate pipelines with their own failure modes and do not share this one's design. - Generative image creation, which brings a rented model, a cost per image and a rights question per output. - A drag and drop canvas, since the value here is that a template renders the same way every time and a canvas is the opposite of that. ### README must contain - That the background removal model is named in configuration, why the library default is not used, and where its licence is recorded - That rendering a font into an image is unrestricted under the Open Font Licence while redistributing the font file carries conditions, and which of the two this project does - That every export writes a sidecar and how to rebuild a graphic from one - Which platform sizes are configured and the date each was checked against the platform's published specification - That renders are deterministic, and the test that proves it ===== ARCHITECTURE.md ===== # Architecture · Adobe Express ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | A headless browser screenshotting an HTML template at an exact pixel size | the layout engine you already know beats any drawing API for text and boxes | | Templates | Plain HTML and CSS files reading values from one brand file | a template you can open in a browser is a template you can debug | | Brand | One file holding fonts, colours, logo and defaults, read by every template | changing the brand should change every graphic, which is the entire point | | Cut-outs | A local background removal model, chosen deliberately and recorded | the default model in the popular library is not free for commercial use | | Provenance | A sidecar file per export listing fields, template version and asset licences | you will be asked what is in a graphic, and the answer should not be a memory | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Render | Headless screenshot, readiness and determinism. | Any engine, provided two renders match byte for byte. | | Brand | Fonts, colours and the ban on literals. | Any format, provided templates cannot declare their own. | | Fit | Measured text fitting and failure by field name. | Any fitter, provided it measures rather than guesses. | | Cut-outs | Model choice and the licence record. | Any model whose licence permits your use, recorded in configuration. | | Provenance | Sidecars, manifests and rebuilds. | Any format, provided a rebuild is tested rather than assumed. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `TEMPLATE_DIR` · required · HTML and CSS templates. Each declares its own size. - `BRAND_FILE` · required · Fonts, colours and logo. The only place these are defined. - `OUTPUT_DIR` · required · Exports and their sidecars, named predictably by date and template. - `CUTOUT_MODEL` · required · Named explicitly. The library default is not permitted here. - `PROJECT_USE` · required · Commercial or personal. A commercial project refuses non-commercial model weights. - `DEVICE_PIXEL_RATIO` · required · Fixed, because it is part of what makes two renders identical. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Adobe Express product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A headless browser screenshotting an HTML template at an exact pixel size, Plain HTML and CSS files reading values from one brand file, One file holding fonts, colours, logo and defaults, read by every template, A local background removal model, chosen deliberately and recorded, A sidecar file per export listing fields, template version and asset licences. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. - Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. ===== MILESTONES.md ===== # Delivery milestones · Adobe Express Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The same pixels every time Rendering a template twice with the same values produces identical files. ### Steps 1. Wait for fonts and images before the shot The load event is not the same as ready to photograph. Files: `src/render.js`, `tests/determinism.test.js` 2. Fix the pixel ratio and the viewport Size is part of the template identity. Files: `src/render.js` 3. Fail loudly on a missing asset A silently wrong graphic is worse than no graphic. Files: `src/assets.js` ### Done when - [ ] Twenty consecutive renders are byte-identical - [ ] A throttled font never yields fallback text - [ ] Output dimensions match the template exactly - [ ] A missing asset fails with a named error ### Watch out - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. ## M2 · One brand file that every template reads Changing a colour or a font once changes every graphic on the next render. ### Steps 1. Expose the brand as custom properties One source for colour and type, read by every template. Files: `src/brand.js`, `templates/_base.css` 2. Fail the build on a hard-coded colour Drift starts with one exception nobody removed. Files: `tests/no-literals.test.js` 3. Re-render from sidecars after a brand change The old graphics are the ones that look wrong first. Files: `src/rerender.js` ### Done when - [ ] A hard-coded colour or font fails the build - [ ] A brand change reaches every template - [ ] Font sources are recorded in the brand file - [ ] Past exports re-render from their sidecars ### Watch out - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. ## M3 · Real text in a fixed box A long headline and a short one both look deliberate. ### Steps 1. Measure, then fit within a declared range Guessing from character count breaks on the first unusual string. Files: `src/fit.js`, `tests/fit.test.js` 2. Declare a minimum size and a line limit per field Unreadably small is a failure, not a solution. Files: `templates/schema.json` 3. Test with emoji, long words and right-to-left text All three appear in real names. Files: `tests/text-cases.test.js` ### Done when - [ ] An over-long headline fits or fails by name - [ ] Emoji and unbreakable words do not overflow - [ ] Text never goes below the minimum size - [ ] Right-to-left strings render correctly ### Watch out - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. ## M4 · Cut-outs, and the licence that comes with them Background removal runs locally with a model you chose on purpose and recorded. ### Steps 1. Name the model, never take the default The default is the one with the commercial restriction. Files: `config/models.json`, `src/cutout.js` 2. Record the licence beside every output You will be asked, possibly by a client's lawyer. Files: `src/assets.js`, `src/sidecar.js` 3. Refuse a non-commercial model on a commercial project Better a failed render than a licence problem. Files: `src/cutout.js`, `tests/licence.test.js` ### Done when - [ ] No library default model is ever used - [ ] The model licence appears in every sidecar - [ ] A commercial project refuses a non-commercial model - [ ] A failed removal falls back rather than mangles ### Watch out - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ## M5 · Batches, sizes and a graphic you can rebuild (production only) One set of values produces every size you need, and any past graphic can be regenerated. ### Steps 1. Keep platform sizes in configuration with a checked date They change, and a constant in a template never gets rechecked. Files: `config/sizes.json` 2. Write a sidecar for every export The sidecar is what makes a rebuild possible. Files: `src/sidecar.js` 3. Provide a rebuild command and test it A rebuild that nobody has run is a hope about the future. Files: `src/rebuild.js`, `tests/rebuild.test.js` ### Done when - [ ] A batch renders every configured size with sidecars - [ ] A rebuild from a sidecar is byte-identical - [ ] Sizes carry and print their checked date - [ ] Exports open with correct colours elsewhere ### Watch out - Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. ===== OPERATIONS.md ===== # Operations · Adobe Express ## Backup Templates, the brand file, the fonts and the assets table. Outputs are regenerable from sidecars, which is exactly why the sidecars matter more than the images. ## Restore Restore the repository, rebuild one past graphic from its sidecar, and compare it against the original. That comparison is also the test that the whole provenance idea works. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local tool the suite is the monitoring: the determinism test, the no-literals test, the text cases, and the refusal of a non-commercial model on a commercial project. ## Incident checklist The two incidents are a graphic in the wrong font and a graphic containing something you were not licensed to use. The determinism work prevents the first; the assets table and sidecars answer the second in minutes rather than in an afternoon of archaeology. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Twenty consecutive renders are confirmed byte-identical - [ ] A hard-coded colour is confirmed to fail the build - [ ] An over-long headline is confirmed to fit or fail by name - [ ] A commercial project is confirmed to refuse non-commercial weights - [ ] A rebuild from a sidecar is confirmed byte-identical ## Launch constraint Do not market omitted Adobe Express capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. HTML and CSS templates. Each declares its own size. TEMPLATE_DIR=./templates # Required. Fonts, colours and logo. The only place these are defined. BRAND_FILE=./brand.json # Required. Exports and their sidecars, named predictably by date and template. OUTPUT_DIR=~/Graphics # Required. Named explicitly. The library default is not permitted here. CUTOUT_MODEL=a permissively licensed segmentation model name # Required. Commercial or personal. A commercial project refuses non-commercial model weights. PROJECT_USE=commercial # Required. Fixed, because it is part of what makes two renders identical. DEVICE_PIXEL_RATIO=2
# Adobe Express · indie build A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | A headless browser screenshotting an HTML template at an exact pixel size | the layout engine you already know beats any drawing API for text and boxes | | Templates | Plain HTML and CSS files reading values from one brand file | a template you can open in a browser is a template you can debug | | Brand | One file holding fonts, colours, logo and defaults, read by every template | changing the brand should change every graphic, which is the entire point | | Cut-outs | A local background removal model, chosen deliberately and recorded | the default model in the popular library is not free for commercial use | | Provenance | A sidecar file per export listing fields, template version and asset licences | you will be asked what is in a graphic, and the answer should not be a memory | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the renderer and the batch command. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **A headless browser automation library with a bundled browser** · free - Why: The render is a screenshot of a real page, which is how you get text layout that is correct rather than approximated. - Get it: Install the library and let it download its browser. - Verify: A blank page screenshots at an exact requested size - [ ] **A local background removal library and a deliberately chosen model** · free for the permissive models; a commercial agreement for some others - Why: The library is permissive but its models are not uniformly so, and the default one requires a paid agreement for commercial use. - Get it: Install the library, read its model list and its licence notes, then choose a model and record why. - Verify: The chosen model name and its licence are in configuration before any cut-out is produced - [ ] **Fonts you have the right to use, with their licence files** · free for open fonts; whatever the foundry charges otherwise - Why: Rendering is unrestricted under an open font licence but the file itself travels with conditions, and a client font may be neither. - Get it: Download the font files with their licence text and keep both in the repository. - Verify: Every font in the brand file has a licence recorded in the assets table - [ ] **Your output sizes, with the date you checked them** · free - Why: Platform image specifications change and a size baked into a template is never revisited, so the date is part of the data. - Get it: Look up the current published specification for each platform you export to and record the dimensions with the date. - Verify: Every size in configuration carries a checked date ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. - Adobe Stock/assets - PDF/video tooling - brand kits - Creative Cloud integration - mobile apps - licensing coverage If one of those is essential to you, that is the reason to keep paying for Adobe Express, and the README should say so rather than pretend.
# Build brief · Adobe Express The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a template renderer that turns a brand file and a handful of fields into exact-size graphics you can re-render later to replace Adobe Express. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An export tool is a licensing machine, and the licence questions are not theoretical. The background removal library most people reach for is itself permissively licensed, but its own documentation says the model weights carry their own licences and tells you to check before using any model commercially, and its default model is released for non-commercial use with commercial use requiring a paid agreement. Two of the alternatives it offers are cleanly permissive, and at least one sits on a training dataset whose terms forbid commercial use, which makes the weights unclear rather than safe. So the model is a recorded choice, not a default you inherited. Fonts work the other way round and people get this backwards. Under the Open Font Licence, rendering text into a graphic is unrestricted and the resulting artwork is yours; it is only redistributing, bundling or modifying the font file itself that brings conditions, and the licence is explicit that font software may not be sold by itself and that a modified version may not keep the reserved font name. So rendering into your exports is fine, and shipping the font files with your project needs the notice to travel with them. Therefore: every font, image and model in the project has a recorded licence and source, the manifest is written by the build rather than by hand, and an export tells you what went into it. ### Stack (fixed, do not substitute) - Rendering: A headless browser screenshotting an HTML template at an exact pixel size · the layout engine you already know beats any drawing API for text and boxes - Templates: Plain HTML and CSS files reading values from one brand file · a template you can open in a browser is a template you can debug - Brand: One file holding fonts, colours, logo and defaults, read by every template · changing the brand should change every graphic, which is the entire point - Cut-outs: A local background removal model, chosen deliberately and recorded · the default model in the popular library is not free for commercial use - Provenance: A sidecar file per export listing fields, template version and asset licences · you will be asked what is in a graphic, and the answer should not be a memory ### Data model (create this before Phase 1) - templates(id, name, width, height, version, html_path, fields_json) · size is part of the identity, not a setting - brand(fonts_json, colours_json, logo_path, licence_notes) · one record, read by every render - assets(id, kind, path, sha256, source, licence_name, licence_url, commercial_ok, added_at) · kind is font, image or model - renders(id, template_id, template_version, field_values_json, asset_ids_json, output_path, rendered_at, duration_ms) - sidecars(render_id, json_path) · the file written beside every export so it can be reproduced - The assets table is the one that stops this being a tool that quietly puts a non-commercial model's output on a billboard. ### Phase 1 · The same pixels every time Build: Load the template in a headless browser at the exact width and height with a fixed device pixel ratio. Wait for fonts to finish loading and for every image to decode before the screenshot. Write the file, then compare two renders byte for byte in a test. Trap: Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. Done when: Two renders of one template with identical values are byte-identical, tested twenty times in a row. A render with a slow-loading font never produces fallback text, tested by throttling the font request. Output dimensions match the template exactly at the configured pixel ratio, checked by reading the file header. A missing font or image fails the render with a named error instead of producing a silently wrong graphic. Do not build yet: No brand file, text fitting, cut-outs or batch export yet. ### Phase 2 · One brand file that every template reads Build: Put fonts, colours, logo and spacing defaults in one file, exposed to templates as custom properties. No template hard-codes a colour or a font family. Re-render every past export from its sidecar after a brand change and show the differences. Trap: Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. Done when: A template declaring a literal colour or font family fails the build, tested with a deliberate one. Changing a brand colour and re-rendering changes every affected graphic, verified by comparing outputs. The brand file records which font files are used and where they came from. Re-rendering a past export from its sidecar after a brand change succeeds and produces a visibly updated file. Do not build yet: No text fitting, cut-outs or batch export yet. ### Phase 3 · Real text in a fixed box Build: Fit text by measuring it in the page and reducing the size within a declared range, rather than by guessing from character count. Declare per field a minimum size, a maximum number of lines, and what happens when it still does not fit. Test with a name containing emoji, a long compound word, and a right-to-left string. Trap: Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. Done when: A headline three times the expected length either fits within the declared line limit or fails with the field named, never overflows, tested with both. Emoji and a long unbreakable word are both handled without overflow, tested. Text never renders below the declared minimum size. A right-to-left string renders in the correct direction with punctuation in the right place. Do not build yet: No cut-outs, batch export or provenance yet. ### Phase 4 · Cut-outs, and the licence that comes with them Build: Run background removal locally with the model named in configuration, never the library default. Record the model, its licence name, its licence URL and whether commercial use is permitted in the assets table. Handle the edge quality problem by offering a matte adjustment, and fall back to using the original image when removal fails rather than exporting a mangled cut-out. Trap: Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. Done when: The model is named in configuration and no code path uses a library default, verified by review. Every render using a cut-out records the model and its licence in the sidecar. A project marked commercial refuses to run a model recorded as non-commercial, tested. A failed removal leaves the original image in place and marks the render, rather than exporting a partial cut-out. Do not build yet: No batch export or platform sizes yet. ### Phase 5 · Batches, sizes and a graphic you can rebuild Build: Render one set of field values across several template sizes in a batch, writing a sidecar for each. Take the target dimensions from a configuration file with the date you checked each platform's published specification, because those change. Name outputs predictably, write in the standard colour space, and provide a command that rebuilds a graphic from its sidecar. Trap: Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. Done when: One batch renders the same values across every configured size and writes a sidecar per output, tested. Rebuilding from a sidecar reproduces a byte-identical file when the brand and template version are unchanged, tested. Sizes carry the date the platform specification was last checked and the batch prints it. Exports are written in the standard colour space and open with correct colours in another application, checked once by eye. Do not build yet: Nothing after this. ### Out of scope (and why) - Stock photography and premium templates, which are the licensed library you are paying for and cannot be reproduced by writing code. - Video and PDF editing, which are separate pipelines with their own failure modes and do not share this one's design. - Generative image creation, which brings a rented model, a cost per image and a rights question per output. - A drag and drop canvas, since the value here is that a template renders the same way every time and a canvas is the opposite of that. ### README must contain - That the background removal model is named in configuration, why the library default is not used, and where its licence is recorded - That rendering a font into an image is unrestricted under the Open Font Licence while redistributing the font file carries conditions, and which of the two this project does - That every export writes a sidecar and how to rebuild a graphic from one - Which platform sizes are configured and the date each was checked against the platform's published specification - That renders are deterministic, and the test that proves it
# Agent instructions · Adobe Express indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A headless browser screenshotting an HTML template at an exact pixel size, Plain HTML and CSS files reading values from one brand file, One file holding fonts, colours, logo and defaults, read by every template, A local background removal model, chosen deliberately and recorded, A sidecar file per export listing fields, template version and asset licences. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial.
# Build plan · Adobe Express A template renderer whose hard problems are producing the same pixels every time despite fonts and images loading asynchronously, fitting real text into a fixed box, and keeping a licence record for every asset that ends up in an export. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The same pixels every time Rendering a template twice with the same values produces identical files. ### Steps 1. Wait for fonts and images before the shot The load event is not the same as ready to photograph. Files: `src/render.js`, `tests/determinism.test.js` 2. Fix the pixel ratio and the viewport Size is part of the template identity. Files: `src/render.js` 3. Fail loudly on a missing asset A silently wrong graphic is worse than no graphic. Files: `src/assets.js` ### Done when - [ ] Twenty consecutive renders are byte-identical - [ ] A throttled font never yields fallback text - [ ] Output dimensions match the template exactly - [ ] A missing asset fails with a named error ### Watch out - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. ## Phase 2 · One brand file that every template reads Changing a colour or a font once changes every graphic on the next render. ### Steps 1. Expose the brand as custom properties One source for colour and type, read by every template. Files: `src/brand.js`, `templates/_base.css` 2. Fail the build on a hard-coded colour Drift starts with one exception nobody removed. Files: `tests/no-literals.test.js` 3. Re-render from sidecars after a brand change The old graphics are the ones that look wrong first. Files: `src/rerender.js` ### Done when - [ ] A hard-coded colour or font fails the build - [ ] A brand change reaches every template - [ ] Font sources are recorded in the brand file - [ ] Past exports re-render from their sidecars ### Watch out - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. ## Phase 3 · Real text in a fixed box A long headline and a short one both look deliberate. ### Steps 1. Measure, then fit within a declared range Guessing from character count breaks on the first unusual string. Files: `src/fit.js`, `tests/fit.test.js` 2. Declare a minimum size and a line limit per field Unreadably small is a failure, not a solution. Files: `templates/schema.json` 3. Test with emoji, long words and right-to-left text All three appear in real names. Files: `tests/text-cases.test.js` ### Done when - [ ] An over-long headline fits or fails by name - [ ] Emoji and unbreakable words do not overflow - [ ] Text never goes below the minimum size - [ ] Right-to-left strings render correctly ### Watch out - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. ## Phase 4 · Cut-outs, and the licence that comes with them Background removal runs locally with a model you chose on purpose and recorded. ### Steps 1. Name the model, never take the default The default is the one with the commercial restriction. Files: `config/models.json`, `src/cutout.js` 2. Record the licence beside every output You will be asked, possibly by a client's lawyer. Files: `src/assets.js`, `src/sidecar.js` 3. Refuse a non-commercial model on a commercial project Better a failed render than a licence problem. Files: `src/cutout.js`, `tests/licence.test.js` ### Done when - [ ] No library default model is ever used - [ ] The model licence appears in every sidecar - [ ] A commercial project refuses a non-commercial model - [ ] A failed removal falls back rather than mangles ### Watch out - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ## Not in this build - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. ## After v1, if you want it - A contact sheet of every template rendered with sample values, which catches brand drift at a glance - A second brand file for a client, since everything already reads from one place - Animated exports of the same templates, once the still renders have been stable for a month
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. HTML and CSS templates. Each declares its own size. TEMPLATE_DIR=./templates # Required. Fonts, colours and logo. The only place these are defined. BRAND_FILE=./brand.json # Required. Exports and their sidecars, named predictably by date and template. OUTPUT_DIR=~/Graphics # Required. Named explicitly. The library default is not permitted here. CUTOUT_MODEL=a permissively licensed segmentation model name # Required. Commercial or personal. A commercial project refuses non-commercial model weights. PROJECT_USE=commercial # Required. Fixed, because it is part of what makes two renders identical. DEVICE_PIXEL_RATIO=2
# Adobe Express · product brief ## Problem You can build a quick image editor and template exporter, but Adobe's stock/licensing, PDF/media tooling, brand assets, and Creative Cloud tie-ins are the product. ## Product outcome A renderer that produces the same pixels every time, fits real text without overflowing, keeps a licence record for every asset that reaches an export, and can rebuild any past graphic from the file written beside it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - canvas/video editor - media processing - image AI API - template assets - storage ## Explicit non-goals for v1 - Stock photography and premium templates. - Video and PDF editing. - Generative image creation. - A drag and drop canvas. - Adobe Stock/assets - PDF/video tooling - brand kits - Creative Cloud integration - mobile apps - licensing coverage ## Success criteria - Twenty consecutive renders are confirmed byte-identical - A hard-coded colour is confirmed to fail the build - An over-long headline is confirmed to fit or fail by name - A commercial project is confirmed to refuse non-commercial weights - A rebuild from a sidecar is confirmed byte-identical
# Build brief · Adobe Express The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a template renderer that turns a brand file and a handful of fields into exact-size graphics you can re-render later to replace Adobe Express. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero An export tool is a licensing machine, and the licence questions are not theoretical. The background removal library most people reach for is itself permissively licensed, but its own documentation says the model weights carry their own licences and tells you to check before using any model commercially, and its default model is released for non-commercial use with commercial use requiring a paid agreement. Two of the alternatives it offers are cleanly permissive, and at least one sits on a training dataset whose terms forbid commercial use, which makes the weights unclear rather than safe. So the model is a recorded choice, not a default you inherited. Fonts work the other way round and people get this backwards. Under the Open Font Licence, rendering text into a graphic is unrestricted and the resulting artwork is yours; it is only redistributing, bundling or modifying the font file itself that brings conditions, and the licence is explicit that font software may not be sold by itself and that a modified version may not keep the reserved font name. So rendering into your exports is fine, and shipping the font files with your project needs the notice to travel with them. Therefore: every font, image and model in the project has a recorded licence and source, the manifest is written by the build rather than by hand, and an export tells you what went into it. ### Stack (fixed, do not substitute) - Rendering: A headless browser screenshotting an HTML template at an exact pixel size · the layout engine you already know beats any drawing API for text and boxes - Templates: Plain HTML and CSS files reading values from one brand file · a template you can open in a browser is a template you can debug - Brand: One file holding fonts, colours, logo and defaults, read by every template · changing the brand should change every graphic, which is the entire point - Cut-outs: A local background removal model, chosen deliberately and recorded · the default model in the popular library is not free for commercial use - Provenance: A sidecar file per export listing fields, template version and asset licences · you will be asked what is in a graphic, and the answer should not be a memory ### Data model (create this before Phase 1) - templates(id, name, width, height, version, html_path, fields_json) · size is part of the identity, not a setting - brand(fonts_json, colours_json, logo_path, licence_notes) · one record, read by every render - assets(id, kind, path, sha256, source, licence_name, licence_url, commercial_ok, added_at) · kind is font, image or model - renders(id, template_id, template_version, field_values_json, asset_ids_json, output_path, rendered_at, duration_ms) - sidecars(render_id, json_path) · the file written beside every export so it can be reproduced - The assets table is the one that stops this being a tool that quietly puts a non-commercial model's output on a billboard. ### Phase 1 · The same pixels every time Build: Load the template in a headless browser at the exact width and height with a fixed device pixel ratio. Wait for fonts to finish loading and for every image to decode before the screenshot. Write the file, then compare two renders byte for byte in a test. Trap: Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. Done when: Two renders of one template with identical values are byte-identical, tested twenty times in a row. A render with a slow-loading font never produces fallback text, tested by throttling the font request. Output dimensions match the template exactly at the configured pixel ratio, checked by reading the file header. A missing font or image fails the render with a named error instead of producing a silently wrong graphic. Do not build yet: No brand file, text fitting, cut-outs or batch export yet. ### Phase 2 · One brand file that every template reads Build: Put fonts, colours, logo and spacing defaults in one file, exposed to templates as custom properties. No template hard-codes a colour or a font family. Re-render every past export from its sidecar after a brand change and show the differences. Trap: Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. Done when: A template declaring a literal colour or font family fails the build, tested with a deliberate one. Changing a brand colour and re-rendering changes every affected graphic, verified by comparing outputs. The brand file records which font files are used and where they came from. Re-rendering a past export from its sidecar after a brand change succeeds and produces a visibly updated file. Do not build yet: No text fitting, cut-outs or batch export yet. ### Phase 3 · Real text in a fixed box Build: Fit text by measuring it in the page and reducing the size within a declared range, rather than by guessing from character count. Declare per field a minimum size, a maximum number of lines, and what happens when it still does not fit. Test with a name containing emoji, a long compound word, and a right-to-left string. Trap: Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. Done when: A headline three times the expected length either fits within the declared line limit or fails with the field named, never overflows, tested with both. Emoji and a long unbreakable word are both handled without overflow, tested. Text never renders below the declared minimum size. A right-to-left string renders in the correct direction with punctuation in the right place. Do not build yet: No cut-outs, batch export or provenance yet. ### Phase 4 · Cut-outs, and the licence that comes with them Build: Run background removal locally with the model named in configuration, never the library default. Record the model, its licence name, its licence URL and whether commercial use is permitted in the assets table. Handle the edge quality problem by offering a matte adjustment, and fall back to using the original image when removal fails rather than exporting a mangled cut-out. Trap: Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. Done when: The model is named in configuration and no code path uses a library default, verified by review. Every render using a cut-out records the model and its licence in the sidecar. A project marked commercial refuses to run a model recorded as non-commercial, tested. A failed removal leaves the original image in place and marks the render, rather than exporting a partial cut-out. Do not build yet: No batch export or platform sizes yet. ### Phase 5 · Batches, sizes and a graphic you can rebuild Build: Render one set of field values across several template sizes in a batch, writing a sidecar for each. Take the target dimensions from a configuration file with the date you checked each platform's published specification, because those change. Name outputs predictably, write in the standard colour space, and provide a command that rebuilds a graphic from its sidecar. Trap: Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output. Done when: One batch renders the same values across every configured size and writes a sidecar per output, tested. Rebuilding from a sidecar reproduces a byte-identical file when the brand and template version are unchanged, tested. Sizes carry the date the platform specification was last checked and the batch prints it. Exports are written in the standard colour space and open with correct colours in another application, checked once by eye. Do not build yet: Nothing after this. ### Out of scope (and why) - Stock photography and premium templates, which are the licensed library you are paying for and cannot be reproduced by writing code. - Video and PDF editing, which are separate pipelines with their own failure modes and do not share this one's design. - Generative image creation, which brings a rented model, a cost per image and a rights question per output. - A drag and drop canvas, since the value here is that a template renders the same way every time and a canvas is the opposite of that. ### README must contain - That the background removal model is named in configuration, why the library default is not used, and where its licence is recorded - That rendering a font into an image is unrestricted under the Open Font Licence while redistributing the font file carries conditions, and which of the two this project does - That every export writes a sidecar and how to rebuild a graphic from one - Which platform sizes are configured and the date each was checked against the platform's published specification - That renders are deterministic, and the test that proves it
# Architecture · Adobe Express ## Stack | Part | Choice | Why | | --- | --- | --- | | Rendering | A headless browser screenshotting an HTML template at an exact pixel size | the layout engine you already know beats any drawing API for text and boxes | | Templates | Plain HTML and CSS files reading values from one brand file | a template you can open in a browser is a template you can debug | | Brand | One file holding fonts, colours, logo and defaults, read by every template | changing the brand should change every graphic, which is the entire point | | Cut-outs | A local background removal model, chosen deliberately and recorded | the default model in the popular library is not free for commercial use | | Provenance | A sidecar file per export listing fields, template version and asset licences | you will be asked what is in a graphic, and the answer should not be a memory | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Render | Headless screenshot, readiness and determinism. | Any engine, provided two renders match byte for byte. | | Brand | Fonts, colours and the ban on literals. | Any format, provided templates cannot declare their own. | | Fit | Measured text fitting and failure by field name. | Any fitter, provided it measures rather than guesses. | | Cut-outs | Model choice and the licence record. | Any model whose licence permits your use, recorded in configuration. | | Provenance | Sidecars, manifests and rebuilds. | Any format, provided a rebuild is tested rather than assumed. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `TEMPLATE_DIR` · required · HTML and CSS templates. Each declares its own size. - `BRAND_FILE` · required · Fonts, colours and logo. The only place these are defined. - `OUTPUT_DIR` · required · Exports and their sidecars, named predictably by date and template. - `CUTOUT_MODEL` · required · Named explicitly. The library default is not permitted here. - `PROJECT_USE` · required · Commercial or personal. A commercial project refuses non-commercial model weights. - `DEVICE_PIXEL_RATIO` · required · Fixed, because it is part of what makes two renders identical. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Adobe Express product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A headless browser screenshotting an HTML template at an exact pixel size, Plain HTML and CSS files reading values from one brand file, One file holding fonts, colours, logo and defaults, read by every template, A local background removal model, chosen deliberately and recorded, A sidecar file per export listing fields, template version and asset licences. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. - Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output.
# Delivery milestones · Adobe Express Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The same pixels every time Rendering a template twice with the same values produces identical files. ### Steps 1. Wait for fonts and images before the shot The load event is not the same as ready to photograph. Files: `src/render.js`, `tests/determinism.test.js` 2. Fix the pixel ratio and the viewport Size is part of the template identity. Files: `src/render.js` 3. Fail loudly on a missing asset A silently wrong graphic is worse than no graphic. Files: `src/assets.js` ### Done when - [ ] Twenty consecutive renders are byte-identical - [ ] A throttled font never yields fallback text - [ ] Output dimensions match the template exactly - [ ] A missing asset fails with a named error ### Watch out - Screenshotting as soon as the page fires its load event catches the moment before the web font has been applied, so roughly one render in ten comes out in the fallback font with different line breaks, and it is never the one you look at. Wait for the font loading promise and for every image to report itself decoded, then screenshot. ## M2 · One brand file that every template reads Changing a colour or a font once changes every graphic on the next render. ### Steps 1. Expose the brand as custom properties One source for colour and type, read by every template. Files: `src/brand.js`, `templates/_base.css` 2. Fail the build on a hard-coded colour Drift starts with one exception nobody removed. Files: `tests/no-literals.test.js` 3. Re-render from sidecars after a brand change The old graphics are the ones that look wrong first. Files: `src/rerender.js` ### Done when - [ ] A hard-coded colour or font fails the build - [ ] A brand change reaches every template - [ ] Font sources are recorded in the brand file - [ ] Past exports re-render from their sidecars ### Watch out - Templates that carry their own colours drift within a month, because the third one was made by copying the second and then adjusted, and by the time anyone notices there are five nearly-identical blues and no way to find them. Have the render fail when a template declares a colour or font family directly, and keep the check in the suite. ## M3 · Real text in a fixed box A long headline and a short one both look deliberate. ### Steps 1. Measure, then fit within a declared range Guessing from character count breaks on the first unusual string. Files: `src/fit.js`, `tests/fit.test.js` 2. Declare a minimum size and a line limit per field Unreadably small is a failure, not a solution. Files: `templates/schema.json` 3. Test with emoji, long words and right-to-left text All three appear in real names. Files: `tests/text-cases.test.js` ### Done when - [ ] An over-long headline fits or fails by name - [ ] Emoji and unbreakable words do not overflow - [ ] Text never goes below the minimum size - [ ] Right-to-left strings render correctly ### Watch out - Choosing a font size from the number of characters works until somebody types a word that cannot be broken or a string of emoji, and then the headline either overflows the canvas silently or shrinks to something unreadable, and the export still succeeds. Measure the rendered text, honour a minimum size, and fail the render with the field name when the text cannot fit. ## M4 · Cut-outs, and the licence that comes with them Background removal runs locally with a model you chose on purpose and recorded. ### Steps 1. Name the model, never take the default The default is the one with the commercial restriction. Files: `config/models.json`, `src/cutout.js` 2. Record the licence beside every output You will be asked, possibly by a client's lawyer. Files: `src/assets.js`, `src/sidecar.js` 3. Refuse a non-commercial model on a commercial project Better a failed render than a licence problem. Files: `src/cutout.js`, `tests/licence.test.js` ### Done when - [ ] No library default model is ever used - [ ] The model licence appears in every sidecar - [ ] A commercial project refuses a non-commercial model - [ ] A failed removal falls back rather than mangles ### Watch out - Installing the popular background removal library and calling it with defaults uses a model released for non-commercial use, so the first client logo you produce is built on weights you do not have a licence for, and nothing in the tooling tells you. Name the model in configuration, record its licence beside the output, and make the build refuse a model marked as non-commercial when the project is marked commercial. ## M5 · Batches, sizes and a graphic you can rebuild (production only) One set of values produces every size you need, and any past graphic can be regenerated. ### Steps 1. Keep platform sizes in configuration with a checked date They change, and a constant in a template never gets rechecked. Files: `config/sizes.json` 2. Write a sidecar for every export The sidecar is what makes a rebuild possible. Files: `src/sidecar.js` 3. Provide a rebuild command and test it A rebuild that nobody has run is a hope about the future. Files: `src/rebuild.js`, `tests/rebuild.test.js` ### Done when - [ ] A batch renders every configured size with sidecars - [ ] A rebuild from a sidecar is byte-identical - [ ] Sizes carry and print their checked date - [ ] Exports open with correct colours elsewhere ### Watch out - Hard-coding social image dimensions in the templates guarantees that a year later half the exports are the wrong size for the platform they were made for, and the only way to find out is to look at them one by one. Keep sizes in configuration with the date the platform's published specification was checked, and show that date in the batch output.
# Operations · Adobe Express ## Backup Templates, the brand file, the fonts and the assets table. Outputs are regenerable from sidecars, which is exactly why the sidecars matter more than the images. ## Restore Restore the repository, rebuild one past graphic from its sidecar, and compare it against the original. That comparison is also the test that the whole provenance idea works. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local tool the suite is the monitoring: the determinism test, the no-literals test, the text cases, and the refusal of a non-commercial model on a commercial project. ## Incident checklist The two incidents are a graphic in the wrong font and a graphic containing something you were not licensed to use. The determinism work prevents the first; the assets table and sidecars answer the second in minutes rather than in an afternoon of archaeology. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Twenty consecutive renders are confirmed byte-identical - [ ] A hard-coded colour is confirmed to fail the build - [ ] An over-long headline is confirmed to fit or fail by name - [ ] A commercial project is confirmed to refuse non-commercial weights - [ ] A rebuild from a sidecar is confirmed byte-identical ## Launch constraint Do not market omitted Adobe Express capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. HTML and CSS templates. Each declares its own size. TEMPLATE_DIR=./templates # Required. Fonts, colours and logo. The only place these are defined. BRAND_FILE=./brand.json # Required. Exports and their sidecars, named predictably by date and template. OUTPUT_DIR=~/Graphics # Required. Named explicitly. The library default is not permitted here. CUTOUT_MODEL=a permissively licensed segmentation model name # Required. Commercial or personal. A commercial project refuses non-commercial model weights. PROJECT_USE=commercial # Required. Fixed, because it is part of what makes two renders identical. DEVICE_PIXEL_RATIO=2
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because the content and export ecosystem is already there.
xAdobe Stock/assets
xPDF/video tooling
xbrand kits
xCreative Cloud integration
xmobile apps
xlicensing coverage
Don't feel like building it? These folks already made it free.
all 4 free alternatives to Adobe Express →· no votes, no pay-to-list · just what's real
Adobe Express pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | 5 GB storage; 1 social account per network; 10-day version history; video up to 30 minutes; 10 PDF quick actions/month; 5 lifetime presentation generations; 2 lifetime 5-second video generations. |
| premium | $9.99/user | — | 250 generative credits/month; 3 social accounts per network; 30-day history; video up to 2 hours. |
| teams | $4.99/user | $4.17/user | 250 generative credits/user/month and 1 TB storage/user; minimum 2 seats. |
| enterprise | custom | — | Custom seats, storage, security and support; public price is not listed. |
free tier5 GB storage, 1 social account per network, 10-day history, 10 PDF quick actions/month, 5 lifetime presentation generations and 2 lifetime 5-second video generations.
billingPremium is sold monthly; Teams is sold monthly and annually, with a 2-seat minimum
hidden costsTeams requires at least 2 paid seats. Generative-credit limits apply, premium stock/assets can require a paid entitlement, and taxes may be added. Firefly Pro at $19.99/month is a separate bundle that includes Express Premium.
verified 2026-08-14 · source ↗
Vibecode Adobe Express
Kinda. The core of Adobe Express is buildable in a weekend with the prompt on this page, but there are real gaps: Adobe Stock/assets, PDF/video tooling. Read the honest list above before committing.
How much does Adobe Express cost?
Adobe Express costs about $9.99/month (Premium, checked 2026-07-30), which is $119.88 per year.
What do I lose by replacing Adobe Express?
Honestly: Adobe Stock/assets; PDF/video tooling; brand kits; Creative Cloud integration; mobile apps; licensing coverage. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Adobe Express?
Yes: Adobe Express Free (The zero-dollar plan still makes and exports social posts, short videos, and PDFs; bulk resize and proper brand kits remain rent.) Canva Free (The bigger template pantry for graphics, clips, docs, and decks, minus Magic Resize and the grown-up brand controls.) Desygner Free (Templates, basic photo work, PDFs, scheduling, and simple brand assets for three people without a countdown clock.) All 4 curated free alternatives are at vibecodeit.com/adobe-express/alternatives. The prompt is for when you want it exactly your way.