ethan

byRebecca Booden

Can you build a modern search engine and import these indexes and let it grow. Only domains indexed on them. https://search.sofya.co/ https://cl0q.com/ https://chakloon.net/

Landing
Landing

Comments (0)

No comments yet. Be the first!

System Requirements

System Requirements Document for ethan

1. Introduction

ethan is a modern search engine built around a single, legible promise: every result it returns comes from one of three imported indexes — search.sofya.co, cl0q.com, and chakloon.net — and from no other domain. The product imports those existing indexes, makes their content searchable, and lets the corpus grow over time while the "only indexed domains" boundary stays visible and enforced.

The audience is twofold. Search users want to look up information and trust that what they see is drawn only from the indexed set, with the provenance of each result readable at a glance. Index operators import the three existing indexes and extend indexed content over time, keeping the corpus growing without admitting domains outside the indexed set.

The product's intent is trust through legibility rather than novelty: the constraint that only indexed domains are searchable is treated as visible signage, not fine print, and the interface is designed so a user can see which index a result came from and that nothing outside the set is reachable.

Page 1 of 25

2. System Overview

ethan is delivered as a first-party web application with a shared FastAPI backend, a MariaDB database for application-owned persistent data, and a React frontend. The backend owns the search API, the index-import and index-growth logic, and the identity and access control that protects index management. The frontend owns the public landing surface, the search surface, and the operator surfaces for enrollment, verification, and index management.

Two active human actors use the product. Search users reach the public Landing and Search surfaces without an account and run queries against the imported corpus. Index operators enroll through Sign Up, verify through Login, and manage the imported indexes and their growth through the role-restricted Index Management surface.

The current delivery covers: importing the three existing indexes; searching only domains present in those indexes; growing the index over time; and operator self-service enrollment, returning verification, and index management. It does not cover crawling arbitrary new domains, adding indexes beyond the three imported sources, or any search over domains outside the indexed set.

Removed from scope

  • None. No concept was explicitly removed by the user; the "only indexed domains" constraint is a binding inclusion boundary, not a removal of a previously accepted feature.
Page 2 of 25

2a. Product Interpretation and Delivery Boundary

ethan is a first-party application. The search experience, the index-import and growth workflow, and the operator identity lifecycle are all owned and rendered by ethan itself; the three source URLs are content and feature references whose indexes are imported, not provider surfaces that ethan delegates its UI to.

Access is split by lifecycle. Landing and Search are public and require no account: a search user can arrive, read the coverage of the three indexes, and run a query immediately. Index Management is role-restricted and requires an authenticated index operator. Because index management controls durable, shared index state, ethan owns operator identity: a first-time operator enrolls through Sign Up, and a returning operator verifies through Login before reaching Index Management. Sign Up and Login are anonymous entry surfaces; they establish access to the protected surface rather than being protected themselves.

The current horizon is the imported three-index corpus and its growth. Future ideas — such as additional indexes or broader crawling — are not part of the current product and are not represented in any current page or acceptance criterion.

Page 3 of 25

2b. Source Content Inventory

The three authoritative content sources supply the following verified facts, preserved item by item.

https://search.sofya.co/

  • Description: an independent search engine, built from its own crawl of the web.
  • Search input field: "Search the web…".
  • Buttons: "Search", "SafeSearch on", "← Previous", "Next →".
  • Contact email: abuse@sofya.co.
  • Capability: independent search engine using its own web crawl.
  • Flow: user enters a query in the search input; buttons available are Search, SafeSearch toggle, and Previous/Next navigation.
Page 4 of 25

https://cl0q.com/

  • Brand: cl0q — free, independent search.
  • Status: "Going open source soon".
  • Menu items: "Skip to search", "Install app", "Settings", "Display settings", "Theme", "Motion", "Text size".
  • Logo: cl0q logo (image).
  • Search input field: "Search the web…".
  • Sections: "Search tips — power operators", "What is cl0q?", "How the engine works", "We don't log your searches", "Run a crawler (early access)".
  • Operator examples: exact phrase ("…"), OR, -, site:, intitle:, inurl:, -intitle:.
  • Infrastructure filters: ip:, cname:, asn:, org:, and combined stacks such as "asn:13335 intitle:login -site:github.io".
  • Privacy features: no search logging, no cookies, queries discarded immediately, optional accounts (passkey only).
  • Early-access info: contributor program for crawler nodes; crawler node source at codeberg.org/cl0qsearch/crawler-node.
  • Support contact: cl0qsearch@gmail.com.
  • Sites: About, Privacy Policy, Feedback, Report content, Run a crawler, Crawler source, GitHub, Search, API.
  • Capabilities: independent crawler and index; power-operator search support; privacy-preserving design (no search logging, cookie-free); early-access contributor crawler nodes; open source coming soon.
  • Flows: user enters a query and receives search results; user accesses search tips and operator examples; user can toggle display settings (theme, text size, motion reduction); user can run or contribute a crawler node; user sees privacy assurances and optional account settings.
Page 5 of 25

https://chakloon.net/

  • Brand: Chakloon Net.
  • Description: search across the sites of the Chakloon, Mosaic Media, DOM, Sponge and Haydamax networks — news headlines and full-text snapshots of every page.
  • Popular searches: climate change, kyiv, eclipse, news, работу, сонячне затемнення, wordpress, wetter online, nachrichten, новини україни, вибори, total solar eclipse over spain in august, wetter, студия дизайна москва, trading, aftonbladet, grenz, ukranian eclispe simulator, киев, отець богдан, captain tsubasa fanfiction, football, solar eclipse, art.
  • Links: About, Sources, Crawler.
  • Capabilities: search across multiple networked sites (Chakloon, Mosaic Media, DOM, Sponge, Haydamax); provides news headlines and full-text snapshots.
  • Flows: user selects or enters a query, possibly via popular searches; user navigates via About, Sources, or Crawler links.

2c. Page Content and Component Coverage

Page 6 of 25

Landing

  • Information/state: product wordmark "ethan"; the uppercase micro-line "SEARCH · THREE INDEXES · NO OTHER DOMAINS"; a two-line statement ending in the red word "indexed"; a coverage table listing the three imported sources (sofya.co, cl0q.com, chakloon.net) with colour dot, status word, indexed-page count, and last-imported date in tabular figures; a schematic SVG diagram of three coloured rectangles feeding one black-outlined search box.
  • Primary actions: enter a query in the search field embedded in the red block and submit via the "Search" button, which navigates to Search with the query.
  • Supporting actions: navigate to Login and Sign Up from the top text-link bar; follow the coverage table rows to read each source's status.
  • Domain entities: Index Source (domain, colour code, status, indexed-page count, last-imported date).
  • Component responsibilities: poster header (wordmark + micro-line on a 2px rule); asymmetric 9/3 hero (statement left, red search block right); coverage table; schematic diagram; text-only navigation with vertical rules and active-state rule.
  • States: loading — coverage table shows a pending status per source while counts resolve; empty — a source with no imported pages shows status "not imported" and a zero count; success — counts and dates render in tabular figures; error — a source whose status cannot be read shows "unavailable" without blocking the search field; recovery — the coverage table refreshes on revisit.
Page 7 of 25

Search

  • Information/state: breadcrumb strip "ethan / search / results for “…”"; a persistent left rail (3 columns) holding the index legend and filter chips (red sofya.co, yellow cl0q.com, blue chakloon.net); a 9-column results column where each result is a ruled table line with domain in tabular uppercase micro-caps in column one, title and snippet in column two, and source dot plus index name in a fixed right column; result count in tabular figures; Previous/Next pagination.
  • Primary actions: enter or edit a query and submit; select an index filter chip to restrict results to one source; page through results with Previous/Next.
  • Supporting actions: read the index legend; read each result's source dot and index name.
  • Domain entities: Query, Result (domain, title, snippet, source index), Index Source, Result Page.
  • Component responsibilities: query field and Search button; index legend and filter chips; ruled result rows; pagination controls; empty and error messaging.
  • States: loading — a thin progress rule fills while results resolve; empty — a "no results in the indexed domains" message with the query echoed and the legend still visible; success — results render as ruled rows with a 200ms crossfade when results replace results; error — a query failure shows a retry message without losing the query; recovery — retry re-runs the same query; at 768px the rail collapses to a horizontal scrollable chip row and at 375px to a wrapped chip row.
Page 8 of 25

Index Management

  • Information/state: role-restricted operator surface; a coverage table listing each of the three sources as a row with domain, import status, indexed-page count, and last-imported date in tabular numerals; a per-source import control; a growth control to extend indexed content over time; a visible statement that only domains in the imported indexes are included.
  • Primary actions: trigger import of a source's existing index; trigger a growth run to extend indexed content; read per-source status and counts.
  • Supporting actions: refresh status; read the exclusion statement.
  • Domain entities: Index Source, Import Run (status, progress, started/finished time), Indexed Page, Growth Run.
  • Component responsibilities: coverage/departure-board table; import controls; growth control; progress rule (or stepped percentage under reduced motion); exclusion signage.
  • States: loading — import progress rule fills left-to-right and stops when the import finishes; empty — a source not yet imported shows "not imported"; success — status and counts update after an import or growth run; error — a failed import shows a failed status with a retry control and leaves prior counts intact; recovery — retry re-runs the import for that source only.
Page 9 of 25

Login

  • Information/state: anonymous returning-verification surface for an index operator; credential fields; a link to Sign Up for first-time operators.
  • Primary actions: submit credentials to verify identity and reach Index Management.
  • Supporting actions: navigate to Sign Up.
  • Domain entities: Index Operator.
  • Component responsibilities: verification form; error messaging; link to Sign Up.
  • States: loading — submit disabled while verification runs; empty — blank fields with inline required markers; success — redirect to Index Management; error — invalid credentials message without revealing which field failed; recovery — re-submit.

Sign Up

  • Information/state: anonymous first-use enrollment surface for an index operator; enrollment fields; a link to Login for returning operators.
  • Primary actions: submit enrollment to establish an operator identity.
  • Supporting actions: navigate to Login.
  • Domain entities: Index Operator.
  • Component responsibilities: enrollment form; validation messaging; link to Login.
  • States: loading — submit disabled while enrollment runs; empty — blank fields with inline required markers; success — confirmation and route to Login to verify; error — validation or duplicate-identity message; recovery — correct and re-submit.
Page 10 of 25

3. Functional Requirements

FR-1 — Build a modern search engine. As a Search user I should be able to run a query against ethan and receive results, so that I can look up information through a modern search interface. (explicit)

  • Trigger/input: a query submitted from Landing or Search.
  • Observable result: a results page listing matching pages from the imported indexes.
  • Access: public, no account.
  • Failure/recovery: a failed query shows a retry message and preserves the query.
  • Continuation: the user can refine the query, filter by index, or page through results.

FR-2 — Import the existing indexes from the three sources. As an Index operator I should be able to import the existing indexes from https://search.sofya.co/, https://cl0q.com/, and https://chakloon.net/, so that their content becomes searchable in ethan. (explicit)

  • Trigger/input: an import action per source on Index Management.
  • Observable result: the source's status moves to imported and its indexed-page count reflects the imported content.
  • Access: authenticated index operator.
  • Failure/recovery: a failed import shows a failed status with retry and leaves prior counts intact.
  • Continuation: the operator can import another source or start a growth run.
Page 11 of 25

FR-3 — Let the index grow over time. As an Index operator I should be able to extend indexed content over time, so that the corpus keeps growing after the initial import. (explicit)

  • Trigger/input: a growth action on Index Management.
  • Observable result: indexed-page counts increase and new content becomes searchable.
  • Access: authenticated index operator.
  • Failure/recovery: a failed growth run reports failure and preserves existing indexed content.
  • Continuation: the operator can re-run growth or return to the coverage table.

FR-4 — Only domains indexed in the imported indexes are searchable. As a Search user I should only ever receive results from domains present in the imported indexes, so that I can trust that nothing outside the indexed set is reachable. (explicit)

  • Trigger/input: any query on Search.
  • Observable result: every returned result carries a source dot and index name from the three imported sources; no result from an unindexed domain appears.
  • Access: public.
  • Failure/recovery: if a query matches nothing in the indexed set, an explicit "no results in the indexed domains" message is shown rather than results from elsewhere.
  • Continuation: the user can adjust the query or filters.
Page 12 of 25

FR-5 — Index operator self-service enrollment. As a first-time Index operator I should be able to enroll myself through Sign Up, so that I can establish the identity that controls index state. (required_inference)

  • Trigger/input: enrollment form submission on Sign Up.
  • Observable result: an operator identity is created and the operator is routed to Login to verify.
  • Access: anonymous.
  • Failure/recovery: validation or duplicate-identity errors are shown inline; the operator corrects and re-submits.
  • Continuation: proceed to Login.

FR-6 — Index operator returning verification before index management. As a returning Index operator I should verify through Login before reaching Index Management, so that durable index state stays bound to the correct operator. (required_inference)

  • Trigger/input: credential submission on Login.
  • Observable result: successful verification routes the operator to Index Management; failed verification keeps them on Login with an error.
  • Access: anonymous entry; protected state remains unavailable until verified.
  • Failure/recovery: invalid credentials message; re-submit.
  • Continuation: reach Index Management.
Page 13 of 25

FR-7 — Import of the three existing indexes before results reflect them. As a Search user I should see results reflect a source only after its index has been imported, so that the corpus I search matches what has actually been imported. (required_inference)

  • Trigger/input: completion of an import run on Index Management.
  • Observable result: the source's content becomes searchable; before import, its pages do not appear in results.
  • Access: system-supported; observable to the operator on Index Management and to the search user on Search.
  • Failure/recovery: an incomplete import leaves the source's prior state intact.
  • Continuation: the operator can retry the import.

4. User Personas

Page 14 of 25

Search user

  • Product context: arrives at ethan to look up information, expecting results drawn only from the three imported indexes and wanting to see which index each result came from.
  • Primary goal: get relevant results from the indexed domains and trust that nothing outside the set is reachable.
  • Distinct responsibilities: enters queries, reads provenance (source dot and index name) on each result, optionally filters by index, and pages through results.
  • Inputs/decisions: the query text; whether to restrict to one index via a filter chip; whether to continue paging.
  • Interactions with other participants: depends on the Index operator having imported and grown the indexes; sees the resulting coverage reflected in what is searchable.
  • Observable success: a query returns relevant results from the indexed domains, each labelled with its source, and the corpus continues to grow over time.
Page 15 of 25

Index operator

  • Product context: responsible for importing the three existing indexes and keeping the corpus growing, while preserving the boundary that only indexed domains are included.
  • Primary goal: imported content is searchable and the index expands without admitting domains outside the indexed set.
  • Distinct responsibilities: enrolls through Sign Up, verifies through Login, triggers per-source imports, runs growth, and reads per-source status and counts on Index Management.
  • Inputs/decisions: which source to import; when to run growth; whether to retry a failed import.
  • Interactions with other participants: produces the searchable corpus that Search users query; the exclusion boundary it maintains is what Search users rely on.
  • Observable success: each source shows imported status with a page count, growth increases counts, and no unindexed domain becomes searchable.
Page 16 of 25

5. Core User Flows

Flow A — Search user runs a query.

  1. Search user opens Landing and reads the coverage table showing the three imported sources.
  2. Search user enters a query in the search field in the red block and submits via "Search".
  3. ethan navigates to Search with the query and shows the breadcrumb "ethan / search / results for “…”".
  4. A thin progress rule fills while results resolve; results then render as ruled rows, each with domain, title, snippet, source dot, and index name.
  5. Search user optionally selects an index filter chip to restrict results to one source; results crossfade in 200ms.
  6. Search user pages through results with Previous/Next.
  7. If nothing matches, ethan shows "no results in the indexed domains" with the query echoed; the user adjusts the query and continues.

Flow B — Index operator enrolls and verifies.

  1. First-time Index operator opens Sign Up from the Landing navigation.
  2. Operator completes the enrollment form and submits.
  3. ethan creates the operator identity and routes the operator to Login to verify.
  4. Operator submits credentials on Login.
  5. On success, ethan routes the operator to Index Management; on failure, an invalid-credentials message is shown and the operator re-submits.
Page 17 of 25

Flow C — Index operator imports the three indexes.

  1. Verified Index operator opens Index Management and sees the coverage table with each source's status.
  2. Operator triggers import for search.sofya.co; the progress rule fills and stops when the import finishes.
  3. Operator repeats for cl0q.com and chakloon.net.
  4. Each source's status moves to imported and its indexed-page count and last-imported date update in tabular figures.
  5. If an import fails, the source shows a failed status with a retry control and prior counts remain intact; the operator retries that source only.

Flow D — Index operator grows the index.

  1. Verified Index operator opens Index Management.
  2. Operator triggers a growth run.
  3. ethan extends indexed content; counts increase and new content becomes searchable.
  4. Operator returns to the coverage table to confirm updated counts and dates.
  5. If growth fails, ethan reports failure and preserves existing indexed content; the operator re-runs growth.

Flow E — Search user relies on the indexed-domain boundary.

  1. Search user runs a query on Search.
  2. ethan returns only results whose domains are present in the imported indexes, each labelled with its source dot and index name.
  3. Search user reads provenance from the right column and the legend in the rail.
  4. If a query would only match unindexed content, ethan shows the explicit no-results message rather than results from elsewhere.
Page 18 of 25

6. Visuals Colors and Theme

The visual system follows the Vignelli-derived direction: systematic clarity, an index readable like a transit map.

Page 19 of 25
  • Mode: light.
  • Colours by role: background #F4F1EA (warm paper ground, never pure white as the page ground); surface #FFFFFF (result panels); text #111111 (all type and 1px rules); primary #D6202B (Search button, active index tab, focus ring, header top rule, red action block); accent #F2B705 (second index code and "safe search on" state); third index code #1F6FB2 (third index chip only); muted #6B6B66.
  • Proportion: ~80% paper/white, ~12% black rule and type, ~6% red, ~2% yellow/blue. No blue-indigo primary, no gradient, no tinted shadows.
  • Fonts: Archivo throughout. Headings and wordmark Archivo 800 (ExtraBold) with -0.02em tracking and 0.95 leading; section labels Archivo 600 uppercase at 12px with 0.14em tracking; body Archivo 400 at 17px/1.55; numbers (result counts, index totals, pagination) Archivo 700 with tabular figures.
  • Type scale: 1.333 modular — 13/17/23/30/40/54/72; landing display clamp(40px, 9vw, 112px); search results title clamp(20px, 2.4vw, 26px); micro-labels 12px; body 17px; uppercase labels never below 11px.
  • Radius/shape: hard edges only — 0px radius on buttons, inputs, cards, and chips. Structure carried by 1px and 2px black rules: 2px rule under the header, 1px rules between result rows, 1px boxes around index chips. Buttons are solid rectangles with a 2px black border; the primary Search button is solid red with a white label. The only non-rectangular element is the 8px square index dot.
  • Spacing rhythm: 12-column grid, 24px gutters, 40px page margin on desktop and 16px on mobile; persistent 3-column left rail with a 9-column results column.
  • Imagery style: no photography; the interface itself is the visual content — colour-coded index legend, ruled coverage table, and a plain-SVG schematic of three coloured rectangles feeding one black-outlined search box (2px strokes). Favicons, domain glyphs, and index dots are the only other graphics.
Page 20 of 25

7. Signature Design Concept

The public entry is a full-width poster, not a centred SaaS hero. A 2px black rule spans the viewport with "ethan" set flush left in Archivo 800 at clamp(40px, 9vw, 112px) and, flush right on the same baseline, the uppercase micro-line "SEARCH · THREE INDEXES · NO OTHER DOMAINS". Directly beneath, the viewport splits 9/3: the left nine columns hold a two-line statement at clamp(28px, 4vw, 54px) ending in the red word "indexed"; the right three columns are a solid #D6202B block, full-bleed to the right edge, with the white search input and a black-bordered "Search" button cut into it, stacked vertically. Under both, a ruled table lists sofya.co, cl0q.com, and chakloon.net, each with its colour dot, a status word, and an indexed-page number in tabular figures. The dominant element is the oversized wordmark-plus-statement against the red block; the palette roles are paper ground, black type and rules, red as the single action block, and yellow/blue only as source codes. At 375px the red block stacks above the input so no readable text or control is overlapped.

Page 21 of 25

8. Interaction Model & Motion Direction

Interaction Model: Static (direction) Motion Tempo: still Hero Dimensionality: flat

Landing Hero Motion Brief

  • Focal subject: the oversized "ethan" wordmark and the red action block with its embedded search field.
  • Input→transformation→outcome thesis: the user types a query into the white field inside the red block and submits via the black-bordered "Search" button; the poster composition gives way to the ruled Search results surface — no entrance animation, only the mechanical transition into results.
  • Motion vocabulary: instant and mechanical — 120ms colour inversion on hover (white→black for text buttons, red→black for the Search button), a 1px rule thickening to 3px under the active index tab, and a single 200ms crossfade when results replace results. The only continuous motion is the thin progress rule that fills left-to-right during an index import and stops dead when the import finishes.
  • Composed first frame: 2px black rule across the top; "ethan" flush left and the uppercase micro-line flush right on the same baseline; the 9/3 split with the statement left and the red block right; the ruled coverage table beneath.
  • Reduced-motion state: everything is already static; the import progress becomes a stepped percentage instead of a filling rule.
Page 22 of 25

9. Non-Functional Requirements

  • Indexed-domain boundary (explicit): only domains present in the imported indexes may be included; no other domains. This is enforced in the search path and reflected visibly in the UI.
  • Provenance legibility (explicit, from direction): every result carries a source dot and index name; the three indexes appear as a permanent colour-coded legend on Landing, Search, and Index Management.
  • Privacy posture (source-backed, cl0q.com): no search logging, no cookies, queries discarded immediately. ethan's search path should not persist user queries.
  • Durable state (planning scope): index state, import runs, growth runs, and operator identities are persisted in MariaDB.
  • Access control (planning scope): Index Management is role-restricted to authenticated index operators; Landing and Search are public.
  • Responsive legibility (direction): headlines, wordmarks, labels, numbers, and controls stay entirely inside the viewport and their container at 375px, 768px, and 1280px, wrapping or scaling to fit; no element covers readable text or controls.
  • Reduced motion (direction): under prefers-reduced-motion, the interface is fully static and import progress is shown as a stepped percentage.
Page 23 of 25

10. Tech Stack

  • Frontend: React application UI.
  • Backend: shared FastAPI process for application APIs and domain modules (search API, index import and growth, operator identity and access control).
  • Database: MariaDB for application-owned persistent data (index sources, indexed pages, import and growth runs, operator identities).
  • Deployment: docker-compose with the backend, database, and frontend services as provisioned.

11. Assumptions and Constraints

  • Assumption: the three source URLs expose importable indexes; the exact import mechanism (API, export, or crawl of the source's own index) is not specified by the user and is left to implementation. (required_inference)
  • Assumption: "let it grow" means extending indexed content over time within the three imported sources, not adding new sources or crawling arbitrary domains. (required_inference)
  • Constraint (explicit): only domains indexed in the imported indexes are searchable; no other domains.
  • Constraint (planning scope): Index Management is role-restricted; Landing and Search are public.
  • Constraint (direction): hard edges only, no gradients, no rounded corners, no second typeface, no icon-only navigation, no load animation, no parallax.
  • Assumption: operator enrollment is self-service with no invitation or provisioning boundary. (required_inference)
Page 24 of 25

12. Glossary

  • Index: a searchable collection of pages imported from one of the three sources.
  • Index Source: one of search.sofya.co, cl0q.com, or chakloon.net, identified by domain and colour code.
  • Indexed domain: a domain present in an imported index; the only domains ethan may return results from.
  • Import run: the operation that brings a source's existing index into ethan.
  • Growth run: the operation that extends indexed content over time.
  • Index operator: the authenticated actor who imports and grows the indexes.
  • Search user: the public actor who runs queries against the imported corpus.
  • Provenance: the visible source dot and index name that identify which index a result came from.
Page 25 of 25
Live
Landing design preview
Landing: Open Sign Up
Sign Up: 1. Submit enrollment
Sign Up: 2. Correct validation errors
Login: 1. Submit credentials
Login: 2. Re-submit after invalid credentials
Index Management: Read coverage table
Index Management: 1. Import sofya.co
Index Management: Import cl0q.com
Index Management: Import chakloon.net
Index Management: 2. Retry failed import
Index Management: 1. Trigger growth run
Index Management: Confirm updated counts
Index Management: 2. Re-run failed growth
Live
Landing design preview
Landing: Open Sign Up
Sign Up: 1. Submit enrollment
Sign Up: 2. Correct validation errors
Login: 1. Submit credentials
Login: 2. Re-submit after invalid credentials
Index Management: Read coverage table
Index Management: 1. Import sofya.co
Index Management: Import cl0q.com
Index Management: Import chakloon.net
Index Management: 2. Retry failed import
Index Management: 1. Trigger growth run
Index Management: Confirm updated counts
Index Management: 2. Re-run failed growth