Plan: Data-Driven Home Page (home-v2.html → /)
Goal
Turn htmltemplate/home-v2.html (the "Documentation hub" landing page) into the
application's real home page served at /. Every piece of content becomes
data-driven: a new HomePage config model + linked models drive the hero,
featured cards, quick paths, quick-install, and "Recently updated" sections, and
the site list is rendered from the existing DocumentationSite records (extended
with category / icon / featured / order). A new cross-site search endpoint backs
the command palette. Branding is configurable (default "Enezadocs").
The current root URL (config/urls.py:9) redirects to documentation:site_index;
it will instead render the new home page.
Locked decisions
| Decision | Choice |
|---|---|
| Integration | home-v2.html → new Django template documentation/home.html rendered at / by a new HomeView. |
| Data model | HomePage singleton + QuickPath + FeaturedSite + HomeUpdate; extend DocumentationSite with category, icon, featured, order. |
| Recently updated | Curated HomeUpdate model (admin-edited), newest first. |
| Search | New cross-site endpoint GET /search/?q= (root-level), wired to the command palette. |
| Branding | HomePage.brand_name (default "Enezadocs"); logo letter = first char of brand_name. |
| Site card fields | icon/name/category/description derived from DocumentationSite; "guides" count from DocumentationSearchDocument per source; "updated" from site.updated_at via timesince. |
| Featured card links | Derived: first up-to-4 documents of the featured site's navigation tree. |
| Quick install | HomePage fields: title, description, code (Text), bullets (newline-separated Text). |
| Version dropdown | Dropped (no DocumentationVersion model); "Latest release" stat reads HomePage.latest_release. |
| Header/footer nav + legal | Simplified static links (Documentation → /docs/, Changelog → #updates, Sign in/up via allauth, Terms/Privacy placeholders). Not modeled. |
| Home layout | Standalone template (does not extend documentation/base.html, which is the 3-column docs layout). |
| Styling/JS | Extract home-v2 <style> → static/documentation/css/home.css; new home.js; reuse theme.js (IDs already match). |
Files to change / create
apps/documentation/models.py— extendDocumentationSite; addHomePage,QuickPath,FeaturedSite,HomeUpdate.apps/documentation/admin.py— extendDocumentationSiteAdmin; registerHomePage(with inlines) + new models.apps/documentation/views.py— addHomeView,SearchAllView.config/urls.py— root""→HomeView; addsearch/→SearchAllView.apps/documentation/templates/documentation/home.html— new (from home-v2.html).apps/documentation/static/documentation/css/home.css— new (home-v2<style>).apps/documentation/static/documentation/js/home.js— new (palette + search fetch + category filter + copy).- Migration(s) via
makemigrations/migrate(run by implementer).
Ordered tasks
1. Extend DocumentationSite (models.py)
Add fields following the repo convention (one field per line, verbose_name on
every field, closing ) on its own line aligned with the field start):
IconTextChoicesinner class:layers,code,terminal,shield,database,globe,card,box(values match home-v2ICONSkeys).category = CharField(max_length=100, blank=True, verbose_name="category")icon = CharField(max_length=20, choices=Icon.choices, default="layers", verbose_name="icon")featured = BooleanField(default=False, verbose_name="featured")order = PositiveIntegerField(default=0, verbose_name="order")
Add ordering = ["order", "name"] to Meta (keep existing verbose_name/verbose_name_plural).
2. Add HomePage singleton (models.py)
Fields (each on its own line + verbose_name):
- brand_name = CharField(max_length=100, default="Enezadocs", verbose_name="brand name")
- hero_eyebrow = CharField(max_length=100, default="Documentation hub", verbose_name="hero eyebrow")
- hero_title = CharField(max_length=300, default="Every Eneza product, documented in one place.", verbose_name="hero title")
- hero_subtitle = TextField(blank=True, verbose_name="hero subtitle")
- cta_title = CharField(max_length=200, default="Can't find what you need?", verbose_name="cta title")
- cta_subtitle = TextField(blank=True, verbose_name="cta subtitle")
- footer_tagline = TextField(blank=True, verbose_name="footer tagline")
- latest_release = CharField(max_length=50, default="v1.0", verbose_name="latest release")
- quick_install_title = CharField(max_length=200, blank=True, verbose_name="quick install title")
- quick_install_description = TextField(blank=True, verbose_name="quick install description")
- quick_install_code = TextField(blank=True, verbose_name="quick install code")
- quick_install_bullets = TextField(blank=True, verbose_name="quick install bullets", help_text="One bullet per line.")
- created_at / updated_at (auto timestamps).
- class Meta: verbose_name = "home page"; verbose_name_plural = "home page".
- Classmethod load() → HomePage.objects.get_or_create(pk=1, defaults={...}) returns the singleton.
3. Add QuickPath, FeaturedSite, HomeUpdate (models.py)
QuickPath:home = ForeignKey(HomePage, on_delete=CASCADE, related_name="quick_paths", verbose_name="home page"),label = CharField(max_length=200, verbose_name="label"),query = CharField(max_length=200, verbose_name="query"),order = PositiveIntegerField(default=0, verbose_name="order");Meta: ordering = ["order", "id"].FeaturedSite:homeFK (related_name"featured_sites"),site = ForeignKey(DocumentationSite, on_delete=CASCADE, related_name="featured_entries", verbose_name="site"),order, optionaltitle = CharField(blank=True)/subtitle = TextField(blank=True)overrides;Meta: ordering = ["order", "id"].HomeUpdate:homeFK (related_name"updates"),site = ForeignKey(DocumentationSite, on_delete=SET_NULL, null=True, blank=True, related_name="updates", verbose_name="site"),text = CharField(max_length=500, verbose_name="text"),published_at = DateTimeField(default=timezone.now, verbose_name="published at");Meta: ordering = ["-published_at"].
4. Migrations
manage.py makemigrations documentation then manage.py migrate (implementer runs; user validates).
5. Admin (admin.py)
DocumentationSiteAdmin: addcategory,icon,featured,ordertolist_display;list_filter+=("category", "featured");list_editable = ("order", "featured", "category").HomePageAdminwithhas_add_permissionreturningFalseonce aHomePageexists (singleton); inlinesQuickPathInline,FeaturedSiteInline,HomeUpdateInline(allextra = 1). RegisterQuickPath,FeaturedSite,HomeUpdate(inlines suffice; standalone registration optional).
6. Views (views.py)
HomeView(View):
- home = HomePage.load().
- sites = list(DocumentationSite.objects.filter(enabled=True)) (respect ordering).
- Build context: home, sites, categories (distinct non-empty category with counts), featured (each FeaturedSite + derived first-up-to-4 docs via DocumentationService(site, authenticated=request.user.is_authenticated).tree() → flatten() → document nodes), quick_paths, updates, and stats:
- stat_sites = len(sites)
- stat_articles = DocumentationSearchDocument.objects.filter(source__sites__in=sites).count() (or per-source sum)
- stat_categories = len(categories)
- stat_latest = home.latest_release
- Per-site guides count = DocumentationSearchDocument.objects.filter(source=site.source).count().
- Render documentation/home.html.
SearchAllView(View) (JSON):
- q = request.GET.get("q", "").strip().
- For each enabled site: run DocumentationService(site, authenticated=request.user.is_authenticated).search(q) (already scopes private docs) → tag each with type="article", site=site.name, slug=site.slug, href=f"/docs/{site.slug}/{path}/".
- Add site-name matches: type="site", href=f"/docs/{site.slug}/" when q matches name/description/category.
- Return {"results": [...]} (dedupe/limit ~20).
7. URLs (config/urls.py)
- Import
HomeView,SearchAllViewfromdocumentation.views; dropRedirectView. path("docs/", include("documentation.urls"))(unchanged).path("search/", SearchAllView.as_view(), name="search").path("", HomeView.as_view(), name="home")(replaces the redirect).
8. Home template (home.html)
Standalone document mirroring home-v2.html structure, server-rendered:
- <head>: inline no-FOUC theme script + tailwind config (copy from prototype), <link href="{% static 'documentation/css/home.css' %}">, {% block title %}{{ home.brand_name }}{% endblock %}.
- Header: logo letter {{ home.brand_name|slice:":1" }}, "Documentation" → /docs/, "Changelog" → #updates, search button (opens palette), theme toggle (IDs themeBtn/themeSun/themeMoon), auth: if user.is_authenticated show logout form ({% url 'account_logout' %}) else sign-in link ({% url 'account_login' %}).
- Hero: eyebrow/title/subtitle from home; search button (palette); quick-path chips {% for p in quick_paths %} with data-q="{{ p.query }}"; stats grid.
- Featured: "Start here" — render featured cards (mark icon via a template {% include %} or an icon_svg filter; derive up-to-4 doc links + "Open documentation" → /docs/<slug>/); "Quick install" card from home.quick_install_* (code in <pre>, bullets split by newline).
- Sites list: category filter chips (server-rendered with counts, data-cat); site cards {% for site in sites %} with data-cat, icon svg, name, category tag, description, guides guides, updated timesince, arrow → /docs/<slug>/. Include the empty-state block (used by JS filter).
- Recently updated: {% for u in updates %} (site name, icon, text, published_at|timesince).
- CTA + footer (brand, tagline, site links derived from sites, Account links, Terms/Privacy #).
- Command palette markup (unchanged IDs: palette, palInput, palList, palCount) + toast.
- Include {% static 'documentation/js/theme.js' %} and {% static 'documentation/js/home.js' %} at end of body.
9. CSS (home.css)
Create static/documentation/css/home.css containing the <style> block from
home-v2.html (lines 19–131): theme variables, base, buttons, chips, search,
site-card, tag, palette, toast, reveal. Ensure the --warn variable and
scroll-col styles are retained.
10. JS (home.js)
Adapt home-v2's script, removing SITES/ARTICLES/UPDATES arrays and the
esc/iconSvg client rendering:
- Category filter: read #siteList .site-card[data-cat], toggle hidden by
active chip, update #resultCount, show/hide #emptyState; wire chip clicks
and #clearFilters.
- Command palette: open/close (Ctrl-K, Esc, overlay, buttons
palOpen/palOpenHero/palOpenSites/palOpenMobile); on input, debounced
fetch('/search/?q='+encodeURIComponent(q)) → render grouped results
("Sites" / "Articles") with highlight; arrow/enter keyboard nav → navigate to
result.href; "Quick paths" chips open palette seeded with data-q.
- Copy button for the quick-install code block.
- Keep the a[href="#"] neutralizer.
Validation (user-run — agent does not test)
manage.py migrate(new fields + models) thenmanage.py runserver.- In admin: edit
HomePage(brand, hero, CTA, quick install, latest release); set a couple ofDocumentationSiterecords withcategory,icon,featured,order; addQuickPath,FeaturedSite,HomeUpdaterows. - Open
/— verify hero, stats (sites/articles/categories/latest release), featured cards (with derived doc links), sites list (cards + category filter chips + counts + timesince), quick-install code/copy, recently updated, CTA, footer. - Verify header auth (Sign in ↔ Sign out) and that
/accounts/login/,/accounts/signup/links resolve. - Open the palette (Ctrl-K or search buttons), type a query, confirm results from the
/search/?q=endpoint (sites + articles) render and navigate to/docs/<slug>/.... - Verify theme toggle + no-FOUC (dark/light) and mobile drawer behavior.
- Confirm
/docs/(site index listing) still works and old/redirect no longer fires.
Risks / notes
- Model field convention (repo rule): every field on its own line with
verbose_name; multi-line declarations close)on its own line aligned with field start. Apply to all new fields. - Singleton
HomePage: usepk=1+get_or_createinload(); block extra adds in admin so only one row exists. - Source→site mapping for "guides"/articles: a
DocumentationSourcecan back multiple sites; count search docs per source (acceptable for the card badge). - Cross-site search privacy: reuse
DocumentationService.search(q), which already limits anonymous users to public docs, so the home palette does not leak private content. - Featured doc links trigger provider tree reads for each featured site; local sources are cheap and GitHub trees are cached (
docs:site:<id>:tree), so keep featured list small. - Template convention: home is standalone (not
base.html); it must include the no-FOUC script, tailwind config,home.css,theme.js, andhome.jsitself. - icon mapping: keep
DocumentationSite.Iconchoices exactly matching the SVG keys inhome.css/template icon map (layers,code,terminal,shield,database,globe,card,box).
Out of scope
DocumentationVersion/ version dropdown restoration.- Modeling header/footer nav links and legal pages (Terms/Privacy) as data.
- Per-site reading-time ("X min") on featured links (dropped).
- The non-functional inline text search box in the sites section (prototype referenced a missing
#qHero); category chips + cross-site palette cover search. - Frontend build/Tailwind compilation (CDN retained, matching the existing app).
- Testing by the agent (user runs the validation steps above).