• Mahmoud Aglan's avatar
    feat(website): make the builder able to reproduce a real bilingual site · 21792905
    Mahmoud Aglan authored
    Building an existing client site inside the builder surfaced the gaps between
    "the blocks exist" and "the blocks can express a finished design". Each of these
    was found by rendering the target and comparing it, not by reading the code.
    
    Blueprint export/import (v2) now carries the whole design, not a third of it.
    It exported pages only — so importing a design gave you the content with a
    default theme and no navigation, which looks nothing like its source. It now
    carries theme settings and menus too. Menu items record their page by SLUG,
    because a page id means nothing in another tenant's database and would import a
    navigation pointing at whatever happened to hold that id. Tracking identifiers
    are deliberately excluded from the whitelist: importing a design must never
    start reporting one client's traffic into another's account. v1 files still
    import.
    
    Isolation that did not isolate. BlockRenderer catches a failing block so the
    rest of the site survives, but Laravel's View::render() calls flushState() when
    any view throws, which clears the section stack of the page *around* it.
    Rendered inside the layout's @section, one bad block therefore killed the whole
    page at @endsection with an unrelated "Cannot end a section" error — the exact
    opposite of the intent. Blocks are now rendered before the layout runs, where
    there is no open section to corrupt.
    
    Bilingual content reached templates raw. Translatable repeater sub-fields
    (a button label, a card title, a partner name) are stored as ['ar'=>…,'en'=>…]
    and read straight out of the data array, so they arrived at {{ }} as arrays and
    took the block down with "htmlspecialchars(): array given". website_text()
    resolves them, and 49 such reads across 15 block views now use it.
    
    The English site rendered right-to-left. website.css hardcoded
    `direction: rtl` on .website-body, silently overriding the dir attribute the
    layout computes from the locale. Direction now follows the document.
    
    There was no English at all. No lang/ directory existed, so every __() returned
    its Arabic key and English visitors read Arabic form labels, buttons and
    helper text. lang/en.json covers all 125 public-site strings.
    
    Smaller gaps, each of which made a real design impossible to express:
    - 'glass' was a valid navbar template and a forbidden column value; the CHECK
      constraint predated it, so choosing it failed at write time.
    - navbar_cta_text had no English twin, so a bilingual site showed one language's
      button to both audiences.
    - An empty navbar CTA fell back to the default label, so the button could not
      be turned off.
    - An anchor menu item resolved to a bare "#id", which points at nothing from a
      sub-page; it now addresses the homepage in the reader's language.
    - A footer column title was a plain string, so it could not be bilingual; the
      'about' column dropped the social row whenever columns were configured.
    - product_showcase stacked its showcase under a centred headline instead of
      laying out as the split it is.
    - A map field stores coords as ['lat'=>…,'lng'=>…] and the view passed the array
      to urlencode(), killing the block.
    - New: 'stacked' info cards, a footer spacer, heading rules and eyebrows on the
      contact block, and a nowrap on highlighted heading fragments so
      "Welcome to {OC-Sport}" cannot break mid-phrase.
    
    Verified against a local Postgres replica carrying a real 11-page bilingual
    site: 22 page/locale combinations answer 200 with zero logged block failures,
    all 137 block/variant combinations render, reserved and unknown paths still 404,
    the four migrations apply and roll back on both an existing tenant and a
    from-scratch install + seed, and the suite passes (140 tests, 483 assertions).
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    21792905