- new command attaching the agency asset delivery (one Dropbox folder
per product) to the product records: files containing "banner" go to
heroimage (first reference reused/swapped), every other jpg/png -
carousel subfolders included - is APPENDED to productimage with the
full reference list, so the live-site images from 2026-09-04 stay
- folder -> record matching: normalised title, title without the
parenthetical, slug, then IMAGE_ALIASES (Joe's "+1" Vision folder
names map onto the existing records - their banner files still carry
the old product names; 95-Series -> avedia-end-points)
- uploads land in fileadmin/product_import/<slug>/ with REPLACE, so
re-runs are idempotent; inbox migrations/product_images_in/ gitignored
- applied: 39 banners + 14 photos across 42 products, 0 errors,
live-verified (srcset/webp processing included)
- the "MGW Diamond (SDI encoder)" Dropbox folder held the OG blade's
banner (image inspected): moved to the OG product in the inbox, the
appliance deliberately keeps no hero; the Dropbox source still has it
in the wrong folder
- still without records: MGW Diamond/Pico/Pico+ TOUGH and X-View - their
assets wait in the inbox, a re-run attaches them once records exist
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new frontend layout 22 "Solution Group Page" (provider LAYOUT_MAP,
TCA layout item, locallang_pages.xlf; zones hero=colPos1, main=colPos0
like layout 5 so the front end reuses its components)
- new pipeline Import\SolutionGroupPageImporter +
vitec:import-solution-group-workbooks (inbox
migrations/solutiongroups_xlsx_in/, gitignored): reads the agency
"Template 06" group workbooks by column-A label scan, resolves the
group via "Hero H1" -> is_group record -> detail_page (pages must
exist), cursor-based slotNext() allows repeated CTypes per column
- element plan per page: markethero (+#capabilities CTA anchor) + hero
intro paragraph; intro + supporting 50/50; challenges header + 4
text_only cards; capabilities header + 4 alternating horizontal cards
(bullets as <ul>); usecaselist shipped HIDDEN (an empty selection
would list ALL stories - editors pick, then unhide); marketinsights;
prefooter ctabanner. Page properties as on the sub-solution pages,
8 curated keyword sets
- 56 numbered grey placeholders for pages 459-466 (7 slots per page)
- applied to all 8 group pages, live-verified on 459/465; the solutions
branch is now content-complete (groups + 39 details)
- spec v1.24 (revision row, head/footer)
Found on the way: the "Engagament" typo lives in page 465's title AND
slug; the record is spelled correctly. Fix needs coordination with the
sub-solution workbook metas - own task, not done here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Joe's 39 "- Completed" workbooks applied to all 39 sub-solution pages
(#467-#515); both workbook inboxes are now gitignored and the tracked
test workbooks untracked - filled deliveries live in the Nextcloud
- new Import\ProductFamilyAliases: the workbooks reference product
FAMILIES in the product slots; 11 labels map to a representative
product, consulted by BOTH importers after the exact title match and
reported as "-> family alias"
- flip-card icons: dummy SVGs fileadmin/icons/flow/<name>.svg for Joe's
10-name vocabulary, attached by the new attachIconFile() through the
shared idempotent attachFalFile() (extracted from attachUrlFile);
real icons later REPLACE those files in the Filelist (same sys_file
uid), every card follows without a re-import
- image slots: numbered grey placeholders per page and slot
(fileadmin/placeholders/pages/<pid>-NN.png, the id rendered into the
image; generic grey as fallback). An existing reference is never
overwritten; a real URL on re-import swaps the placeholder away
- page properties per sub-solution: og/twitter title + description
mirror the SEO fields, canonical_link points at the page itself,
abstract derived from the intro copy (~300 chars, word boundary),
keywords from the curated PAGE_KEYWORDS set
- spec head-matter fix rides along (version table was stuck at 1.21,
real state 1.23)
Open afterwards: 3 missing Datapath stories + re-apply, quotes on 16
story records (list to Joe), slug slash on page 467.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The selectable recipients are no longer a hand-maintained setting but
every active frontend user of the feedback group (feUserGroup) with a
valid e-mail address; the id is the fe_users uid, so a stored selection
survives users joining or leaving. The `recipients` setting is marked
unused
- Each recipient in the state payload carries label, email and selected
(the stored choice), for a checkbox list with "select all" above the
submit button
- submit accepts the checked ids directly ({"recipients": [uids]}),
validates them against the group, stores and uses them, and reports
the addresses used as sentTo; no selection falls back to mailTo
- migrations/check_fecomment_user.php: all summaries and items of one
user incl. deleted ones, for diagnosis
- New content element "Feedback Overview" (CType fecomment_overview):
the current FE user's feedback state under content.feedbackOverview,
so the overview page renders like any other page instead of a
front-end special case
- Rendered as USER_INT inside the cached page JSON (EXT:headless replaces
the field per request): data is per user, visitors without a session
get {"allowed": false} - verified that nothing leaks from the cache
- One implementation for both consumers: the new FeedbackState service
builds the state payload; the API middleware delegates to it
- State payload (API and element) gained canSubmit and actions (all
write endpoints with method and URL), so the front end needs no
hard-coded endpoints for the submit button below the ticket list
- New site set evomedien/fe-comment carries the TypoScript, added to the
site configuration; icon fecomment-overview
- evo_megamenu_json stays project-neutral: new panel layout "catalog";
for such entries the MenuBuilder dispatches the new PSR-14 event
BuildCatalogEvent and emits whatever a listener sets as `catalog`
(null without a listener). The entry's teaser cards serve as the
featured tiles; the teaser inline field now also shows for "catalog",
and the entry form reloads when the layout changes. README section
"Layout catalog"
- VITEC listener ProvideProductCatalog fills it, modelled on the Ross
Video products menu: main categories below the "Product" root, their
subcategories as groups, products with a plain-text one-line teaser;
same visibility rules as the product list, Product Finder links
(/products?cat=&subcat=), empty categories and groups left out, three
queries for the whole catalog
- Product type-ahead GET /_json/?type=7385&q= (USER_INT page type,
ProductMenuSearchJsonRenderer): Solr restricted to products, last term
completed as a prefix, up to six product cards plus their titles as
autocomplete suggestions; endpoint announced in catalog.search
- Spec v1.23 (clause 7.16: product type-ahead)
The teaser cards field depends on the layout via displayCond; it now appears right after switching to catalog/teasers instead of only after saving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New record type tx_vitec_domain_model_searchcard ("best bets"):
keyword list, priority, optional referenced record (page, product,
story, solution, market, news) and overridable title/teaser/image/link;
hidden/start/end time allow timed campaigns
- SearchCardService delivers content.search.cards on page 1: max. 3,
priority-ordered, deduplicated by URL, independent of filter, facets
and result count. Keywords match as whole words/phrases, case- and
punctuation-insensitive, including one level of the Solr managed
synonyms in both directions (cached for an hour, fail-soft). Record
cards read the record's indexed Solr document, so a card always
matches the record's own search result
- Index fallback for markets and solutions: when the record carries no
image or text, the document takes the intro image (then the hero
background) and the hero subline, intro headline and intro text from
the content blocks of its detail page - selected by CType, so it works
for layout 21 and layout 5 alike; improves results and cards
- Fix: a search request without q returned an empty body instead of the
documented empty shape (stdClass resolved inside the renderer namespace)
- migrations/check_solution_fields.php: shows live/workspace versions of
solution records
- Spec v1.22 (clause 7.16: cards)
List all indexed types (markets and solutions were missing), the PDF
datasheet text extracted through Apache Tika, the /_json/search
endpoint and the current feature set (facets, tile images,
autocomplete).
- Index the text columns of the market/solution Content Blocks and the
vitec_items/vitec_faq_items collection children; story documents now
include their body (inline tt_content children via
tx_vitec_usecase_content, same per-element extraction as pages) -
stories were indexed with subtitle and teaser only until now
- Upgrade EXT:solr 14.0.0-RC1 -> 14.0.2 final (the Solr image moved to a
fresh volume because the configset changed; synonyms restored via REST)
- News decay with age by publication date: crdate is the import date, so
news map datetime to created; boost function gated to news only
- Results carry `image`: processed 640px tile image per type,
root-relative, null for pages and downloads
- Spec v1.21: 7.16 states the /_json prefix and lists `solution` in the
type vocabulary; footer had been stuck at v1.19
- suggest-demo.html fetches through /_json after the URL swap
- New, deliberately separate pipeline for sub-solutions (markets and
solutions must never break each other): generator
vitec:generate-subsolution-workbooks (list from the page tree below
/solutions, 39 workbooks, every sub-solution matched to its record),
SubsolutionWorkbookImporter + CLI vitec:import-subsolution-workbooks,
and a "Sub-Solutions" tab in the VITEC Import module with its own
inbox migrations/subsolutions_xlsx_in/
- Targets the existing template 5 "Solution Detail" without changing
it: hero element in colPos 1, the remaining sections in fixed order in
colPos 0, found again by CType on re-import; existing empty sub-solution
pages are switched to layout 5, missing ones created hidden;
detail_page set on the solution record
- Workbook per the Figma "07 Solution Detail page": hero without video
teaser, intro with headline and without learn-more/portfolio, extended
icon list, own page-layout reference image
- Shared content elements: market hero/intro/insights relabelled
"Market/Solution", market intro gains the optional intro_headline
field (new column, requires extension:setup)
- Fix: DataHandler puts every NEW record with a positive pid at the top
of its column, so pages built in one run came out reversed. Both
importers now track the page order per colPos and write
tt_content.sorting explicitly after the DataHandler run; re-applying
repairs pages imported earlier (verified on the control-room
sub-solution and sub-market page 443)
- migrations/fill_test_subsolution.php: realistic test content from
vitec.com copy and images for the control-room sub-solution
- Docs section 5z
- Extract the complete import logic (label-scan reading, page
find-or-create, slot filling, content-idempotent image attach) from
ImportSubmarketWorkbooksCommand into the shared service
SubmarketWorkbookImporter; the CLI command and the new controller are
thin shells around importFile(), so both entry points cannot drift.
CLI regression verified: identical dry-run report for the reference
workbook (12 elements, page 443)
- New "Sub-Markets" tab in the VITEC Import module
(SubmarketImportController + Import/Submarkets.html): upload multiple
filled workbooks into the SAME inbox the CLI uses
(migrations/submarkets_xlsx_in/), per-file checkboxes, dry-run and
apply buttons, result cards with the familiar report and a direct
link into the page module; backend dry-run verified against the CLI
- Submitted filenames are whitelisted against the actual directory
listing (no path traversal); uploads restricted to sanitized .xlsx
basenames
- Routes submarkets/submarket_upload/submarket_process in Modules.php,
Sub-Markets tab link in the sibling module views, ok/danger badge
colors in backend-import.css
- Docs section 5y (incl. operations note: apply in small batches in the
module, mass runs via CLI - image downloads run inside the web
request)
- Rebuild the widget server-backed: it renders only when GET state
allows (logged-in FE user of the configured group), so the script
tag is safe on public pages; form with mini rich text (bold/italic/
list), file attachment chips and Ctrl+V image paste - a paste lazily
creates the draft feedback, uploads and inserts the image inline
- Note pad badge mirrors the server item count; the pad button links
to the overview page via new extension setting overviewPid, exposed
as overviewUrl in GET state (slug lookup)
- Embed /fileadmin images of notes into the feedback mail as CID
parts: relative srcs are dead in mail clients and dev sits behind
basic auth; attachment links stay absolute URLs
- public/widget-test.html: dev test page, doubles as the reference
implementation of the React login flow (fetch felogin JSON form
incl. __RequestToken, urlencoded POST to /_json + form.action)
- Docs section 5x (fe_comment v2 incl. CSRF login lesson,
templateRootPaths.10 collision, cache:flush before extension:setup)
- Replace the v1 submit endpoint with a full JSON API under
/api/fe-comment/ (state, feedback create/update/delete, uploads,
summary notes/recipients, submit), guarded by frontend login and a
configurable FE user group; unauthorized callers get {"allowed":false}
- New models: tx_fecomment_summary (one open ticket per user, status
open/sent) and tx_fecomment_feedback (url, RTE note, FAL attachments),
both editable in the backend on the ticket storage page
- FeedbackApiMiddleware registered after frontend authentication (the
frontend.user attribute does not exist earlier); uploads land in
fileadmin/fe_comment/summary-<uid>/ with mime/extension check, 20 MB cap
- Submit sends one HTML mail (author block, items with attachment links,
reply-to = FE user mail) to selected recipients with mailTo fallback,
then closes the summary
- felogin wired React-compatible: felogin.pid=520 plus headless FeLogin
JSON templates via site settings - felogin v14 and EXT:headless both
claim templateRootPaths.10, the felogin.view.* settings feed exactly
that index, so the structured JSON form (incl. __RequestToken) wins
- Read the sys_file_reference insert id before syncAttachmentCount():
DBAL throws "no identity value" once the count UPDATE has run
- migrations: drop_fecomment_tables.php (repairs the no-uid tables,
only drops when empty), check_feuser.php (login diagnosis),
fec_login_test.sh / fec_api_test.sh (curl chains incl. CSRF token flow)
thumbnail of the actual image (hero background, intro image, insights
cover), key copy in the summary line, full details expanded; card and
intro-paragraph previews are wrapped in the same mechanism
- New SubmarketPluginPreview for vitec_usecaselist / vitec_productfeature /
vitec_productlist: instead of bare standard boxes they now list the
selected story titles (flagging stories that still lack a quote), the
featured product and the hand-picked products in order
- Container-style collapse for whole page-module COLUMNS: a chevron at the
right of every column header folds a section down to its title row,
state kept per page and colPos in localStorage - the slot view of a
sub-market page folds to six slim bands
- Previews sit under the page module's pointer-events lock and its
click-to-edit layer: pointer events are re-enabled inside the collapse
markup and the toggle runs on pointerdown in the capture phase
- Two more v14 removals fixed on the way, both latent in the OLDER preview
classes too (Usecaseshow, Modelcard would have crashed the page module):
GridColumnItem::getRecord() now returns a Domain\Record object, and
StandaloneView is gone - previews normalize the record via a shared
helper and render through the ViewFactory
frame 'Sub-Market Page' does not contain the Industry Insights band or the
footer, they live as siblings inside the surrounding section. The asset is
now rendered from the section (contentsOnly), so the customer sees the
complete page including Insights and footer; the section label rows fit
unchanged.
per sub-market (25, grouped by the 9 main markets): VITEC-styled Read me,
a Page-layout sheet embedding the Figma design with section markers,
DB-live dropdowns, soft character limits, a hidden Meta sheet and named
input cells. Names must NOT start with a cell-reference token - Excel
silently strips s1_-style names as repairs.
vitec:import-submarket-workbooks (dry-run default, idempotent) finds or
creates the page below its main market (hidden, layout 21), fills the six
colPos slots updating in place and never deleting, links detail_page on
the market record, downloads asset URLs into fileadmin/submarket_import/
for editorial review and writes the flexforms as DataHandler arrays.
Reading is three-layered after a bruising day of real-world Excel: named
ranges first, then a label scan over the locked column A with section
tracking - a customer save stripped ALL defined names and shifted rows,
so fixed addresses are not a contract.
Learned and fixed along the way: DuplicationBehavior moved to
Resource\Enum in v14; sys_file_reference rows via DataHandler need
pid/tablenames/uid_foreign or are dropped silently; handing DataHandler a
fresh NEW reference per run STACKS live references on re-import (caught
by a double-run test), so attachments are now content-idempotent - the
existing live reference is reused or retargeted and surplus duplicates
are deleted via cmdmap; RequestFactory replaces GeneralUtility::getUrl so
download failures carry their reason - which surfaced that the dev basic
auth blocked the server itself (server IP + loopback now allowlisted,
which a future SSR needs anyway) and that the core resource-consistency
check rightly rejects HTML masquerading as PNG.
Verified end to end on Broadcast & Media from a clean slate, twice in a
row: 12 elements in colPos 110-160, layout-21 payload, both images
attached exactly once with publicUrl in the JSON; second run reports
nothing but unchanged.
the customer workflow in mind: the customer fills one XLSX per sub-market,
an importer builds the page).
- Frontend layout 21 'Sub-Market Page' in the existing
BackendLayoutDataProvider: six zones, one per Figma section, emitted as
colPos110..colPos160 - the JSON groups plus appearance.layout are the
front-end contract (spec clause 7.17). A first attempt via page-TSconfig
BackendLayouts was the wrong mechanism next to the provider and was
removed again
- New Content Blocks vitec_markethero, vitec_marketintro,
vitec_marketinsights (field bags, serialized unprefixed under data);
the workflow section reuses intro-paragraph + vitec_card flip cards
- Stories gained quote/quote_person/quote_role (Testimonial tab),
maintained once per story, emitted on every story card; usecaselist
gained the layout value 'testimonials'
- New plugin vitec_productfeature: one referenced product as a full
product card, serialisation shared with the product list (cObj made
protected for the subclass)
- vitec_productlist gained hand-picked product selection: exactly the
chosen products in the editor's order, category filter ignored,
visibility filters kept - closes the long-standing single-select gap
- New CLI vitec:generate-submarket-workbooks: one protected customer
XLSX per sub-market (9 main markets, 25 sub-markets from the Market
category tree) - dropdowns filled live from the database, named ranges
as the importer contract, hidden Meta sheet pinning submarket/group/
record uid, soft character-limit warnings, asset URL fields
Verified end to end on Broadcast & Media: layout-21 payload with colPos110
carrying the hero element and its subline. Spec raised to v1.20.
feedback widget served from /api/fe-comment/widget.js, note pad in
localStorage, submissions mailed via /api/fe-comment/submit with
recipients from the extension configuration; classic sites get the
script injected via AssetCollector when the jsonOutput setting is off.
An enabled master switch serves the widget EMPTY when off (no 404, no
console errors) and rejects submissions - the go-live kill switch, no
front-end build change needed
- URL swap finished: /_json prefix middleware, SPA fallback live with a
dedicated root rule (REQUEST_FILENAME of / is a directory) and a
query-string exception for the TYPO3 v14 install tool entry
(?__typo3_install on the site root - the backend Settings/Environment
modules depend on it); /api/ as a whole stays with TYPO3
- MailMessage::send() no longer exists in v14: fe_comment and the form
EmailDelivery (latently broken since the core upgrade) now send through
the Mailer service. Mail transport moved to SMTP against the local exim
so delivery errors surface instead of bouncing into the blackhole -
exim verifies the envelope sender, which therefore must be a routable
existing address (transport config lives in the untracked settings.php)
- Related-product card texts editable on the product record (table rebuilt
with uid/pid, TCA + inline field, one-time migration script); shared
RteCompare for the module badge and the CLI comparison - batch dry-run
now reports 0 writes instead of flagging every richtext field
- Workbook clarification cases executed: VisionIO-SD14, VisionSC-SD14 and
both Diamond OG records imported (40 fields, live-verified); ArtioSign
and ArtioView created hidden (uid 109/110) via
create_artio_records.php - the product table has no sorting column;
WallControl_10 mapping REVERTED and parked: the dry run revealed
aetria-wallcontrol already carries its own agency workbook, which
WallControl_10 would have overwritten. Manifest now 75 product /
24 family / 1 open
- layout_variant gains the value "flipcard" (onChange: reload)
- "Card Back" palette in the Layout tab directly below layout_variant:
back_headline (Text, max 100) and back_text (Textarea, richtext),
displayCond-gated on the flipcard layout; serialised unconditionally -
the frontend keys the back face on layout_variant, not on field
presence (spec 7.7)
- backend preview: flipcard badge + back-headline line
- repair "[ Missing label ]" in the card selects: layout_variant /
background_variant are columns SHARED with vitec_ctabanner, and the
index-wise Content Blocks merge dropped four card-only values from
the base item list. Overrides/tt_content.php now pins the base column
to the cta-banner lists and hands vitec_card its full lists via
columnsOverrides (respected by FormEngine and DataHandler; an items
override can only extend, never shorten - array_replace_recursive)
- spec v1.19 incl. head-matter fix (version table was stuck at 1.17)
Server follow-up: database:updateschema "*.add,*.change" + cache:flush
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements the 18.09. strategy's phase 1 on top of the existing single
importer (which stays the one write path):
- new vitec:import-product-workbooks: runs vitec:import-product-workbook
per manifest entry (in-process sub-command), aggregates a per-file
report (writes/unchanged/na, URL-mismatch and error flags, details on
-v or failure) plus a summary; without --apply every run is a dry
run; --only filters
- migrations/products_xlsx/ now holds working copies of all 100 agency
workbooks in their category folders (git-ignored) plus the TRACKED
manifest.json: workbook path -> product slug, family entries mark the
K-level landing pages (never imported), product null = unresolved.
Generated from the 18.09. analysis + sitemap column K:
68 product / 17 family / 15 open
- --init-manifest resolves open entries against the full product table
(hidden records included): URL segment/filename vs slug, normalized
title, unique slug prefix; only unambiguous hits are written back
- single command compare is now tolerant of the RTE storage transform
(whitespace collapse + <p>-wrap simulation), fixing the known
every-run-WRITE false positive on re-imports
Post-dry-run hardening, applied the same day (69 products, 785 field
writes across two runs, zero errors, verified against the live JSON):
- PROTECTED_FIELDS title/urltitle in the single command: the stored
module mapping would have overwritten six canonical sitemap-V2
product titles
- the stored mapping predated the 18.09. defaults and silently dropped
`highlights` on every product; the single command now merges
DEFAULT_MAPPING under the stored mapping (defaults as base, stored
as per-field override)
- known limitation for phase 2: the g: composed fields report WRITE on
every run (RTE transform alters ul/strong markup beyond the p-wrap
normalisation) - the change watch must compare against stored
imported values, not the database
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two work packages, one deploy.
Workbook field mapping completed (2026-09-18)
- highlights <- g:Technical Highlight (new spec-row component of the
full agency delivery; composeGroup() is generic, no change needed)
- applications <- g:Why Choose Card (benefit cards as one block)
- seometa <- new composed source x:seometa (Introduction body trimmed
at a word boundary to ~155 chars - the SEO sheet ships no meta
description, a trimmed intro beats an empty tag)
- teaser stays the hero one-liner, settling the varchar(255) question
- fix: "Save mapping" now merges over the stored mapping instead of
overwriting it - an empty dropdown no longer wipes a stored selector
Product model: solutions relation and Resources tab (2026-09-24)
- closes the gaps the full workbook walkthrough exposed (rows the
import deliberately skips because the model had no place for them)
- solutions: MM multi select to tx_vitec_domain_model_solution
(tx_vitec_product_solution_mm, relatedprodukt pattern), emitted by
both product renderers as solution cards in the shared shape
- Resources tab: inline child table tx_vitec_product_resource - per
entry a type (documentation/support), an editable card title/text,
an icon (free text with a value-picker seed set) and one link
(page/url/file/record via the link wizard). Deliberately no Extbase
model: only the UserFunc path reads the rows.
- datasheets: own MM selection of download records below the resource
list (tx_vitec_product_datasheet_mm); emitted into the SAME
resources envelope as ready-made type=datasheet cards (title/teaser/
icon from the record, url = the public /download/<slug> route, uid
fallback - private/hidden downloads 404 as everywhere)
- JSON `resources` (detail payload only, like heroimage): list envelope
{layout, items[]}; a hand-written card whose link is empty or
unresolvable is not emitted; layout switches to carousel above three
cards in total
- solutions hybrid (same day, after analysing all 100 workbooks: their
Solution Cards reference the 8 GROUPS in 98% of cases): new is_group
flag on the solution model, new idempotent CLI
vitec:create-solution-groups (8 group records; title source is
AlignSolutionsCommand::TARGET - now public -, category = the group's
own category, detail_page = the /solutions group page with -N
disambiguation). Group records are invisible to the solution list
and to vitec:align-solutions and only selectable in the product
relation; product solution cards carry isGroup
- spec raised to v1.18 (7.15 member table, 7.13.3 note, revision row;
the footer had been stuck at v1.15 since 1.16/1.17)
Requires on the server:
vendor/bin/typo3 database:updateschema "*.add,*.change"
vendor/bin/typo3 cache:flush
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two work packages, one deploy.
Workbook field mapping completed (2026-09-18)
- highlights <- g:Technical Highlight (new spec-row component of the
full agency delivery; composeGroup() is generic, no change needed)
- applications <- g:Why Choose Card (benefit cards as one block)
- seometa <- new composed source x:seometa (Introduction body trimmed
at a word boundary to ~155 chars - the SEO sheet ships no meta
description, a trimmed intro beats an empty tag)
- teaser stays the hero one-liner, settling the varchar(255) question
- fix: "Save mapping" now merges over the stored mapping instead of
overwriting it - an empty dropdown no longer wipes a stored selector
Product model: solutions relation and Resources tab (2026-09-24)
- closes the gaps the full workbook walkthrough exposed (rows the
import deliberately skips because the model had no place for them)
- solutions: MM multi select to tx_vitec_domain_model_solution
(tx_vitec_product_solution_mm, relatedprodukt pattern), emitted by
both product renderers as solution cards in the shared shape
- Resources tab: inline child table tx_vitec_product_resource - per
entry a type (documentation/support/datasheet), editable card title
and text, an icon (free text with a value-picker seed set) and one
link (page/url/file/record via the link wizard, so the Download tab
works too). Deliberately no Extbase model: only the UserFunc path
reads the rows.
- JSON `resources` (detail payload only, like heroimage): list envelope
{layout, items[]}; a card whose link is empty or unresolvable is not
emitted; layout switches to carousel above three cards
- spec raised to v1.18 (7.15 member table, revision row; the footer had
been stuck at v1.15 since 1.16/1.17)
Requires on the server:
vendor/bin/typo3 database:updateschema "*.add,*.change"
vendor/bin/typo3 cache:flush
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The big product list on /products (all products, no category context)
deliberately delivered the empty successStories envelope; the front end
now renders story cards there too and filters them client-side, so the
element aggregates over the WHOLE product category tree instead:
- new allProductCategoryUids(): root category 'Product' (same detection
as ProductFinderJsonRenderer), children plus descendants depth-first,
fed through the existing successStoriesFor()
- per-category elements are untouched; forCategories keeps carrying the
category uids a story is attached to, so the ?cat=/?subcat= filter
behaves identically on both element kinds
- layout rule unchanged: first contributing category in tree order
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sys_category gains a story selection and a layout (Options tab, alongside
the images field EXT:news puts there), so one editorial decision serves
every product list that renders the category. ProductListJsonRenderer emits
them beside the products in the 7.3 list envelope; each card carries
forCategories - the product categories it came from - so the front end hides
it with the same ?cat=/?subcat= filter it applies to the products. Stories
are collected over the category and its descendants and deduplicated.
Story links follow the existing rule (detail page's parent path plus slug,
which SuccessStoryPathRewrite maps back), taking the page from the element's
FlexForm or the new site setting vitec.storyDetailPid.
Spec raised to 1.17 with clause 7.15.2.1; the header had been left at 1.12
although entries up to 1.15 already existed, so today's two entries were
renumbered to 1.16 and 1.17.
Solr 10 dropped the ExtractingRequestHandler, so a dedicated Tika
container on the Solr VPS (Caddy route /tika/*, own basic-auth
credential; Tika itself has no auth) extracts file text during
indexing: TikaDownloadContentIndexer listens on
BeforeDocumentIsProcessedForIndexingEvent, resolves the file through
the existing DownloadFileResolver and appends the text (mime
whitelist, 30 MB cap, 100k chars, fail-soft) to the document's content
field. Extractions are cached in var/tika-cache keyed on
path+size+mtime - a full re-index of 177 downloads drops from 2:12 min
to 27 s, replacing a file re-extracts naturally. Datasheet
specifications ("625i", "genlock") are now searchable. Spec v1.15.
- new project-neutral extension evo_megamenu_json (Evomedien): a Megamenu
plugin holding entries, columns, items, teaser cards and a featured slot
as nested records; presentation settings travel with the payload so the
front end reads behaviour instead of hard-coding it; published as
page.10.fields.megaMenu, driven by the site settings megamenu.contentUid
and megamenu.storagePid
- vitec:seed-megamenu fills one element from the live structure: 5 entries,
26 columns, 95 items, 4 story cards; items without a page yet are flagged
pending so the front end falls back to the column link
- vitec:debug-sets prints the resolved site set order
- read-only diagnostics: check_megamenu_schema.php, check_typoscript_templates.php
- docs: schema updates run via extension:setup (database:updateschema no
longer exists in v14), and a JSON Accept header activates headless-mixed,
which strips every set-added page field
- vitec:align-solutions: declarative target = the 39 Generic Solutions in 8
group categories under the Solution root (sheet is binding, group typo
Engagament->Engagement fixed). Two legacy records renamed onto their sheet
names instead of deleted (keeps the only editorial teaser/description),
slugs generated for all, market categories stripped from solutions, the
~82 market-specific records soft-deleted (markets link editorially)
- vitec:map-solution-pages: writes matching page uids into detail_page
(unique matches, fill-only); disambiguates the duplicated page branches
by slug suffix and prunes the 10 empty duplicate pages
- read-only diagnostics: check_solutions.php, check_dup_solution_pages.php
- vitec:import-product-images: attach staged photos (fileadmin/user_upload/
products/<slug>/, 116 files harvested from vitec.com and datapath.co.uk
carousels) to image-less products via FAL/DataHandler; fill-up only.
fix_image_reference_fieldname.php repairs the first run's references
(TCA field is productimage, not the JSON key images)
- vitec:link-product-downloads: match the download records imported on
2026-08-06 to products by title (doc-type/brand/role suffix stripping,
alias table for live-site naming deviations, series rules for g45xx/e38xx/
chassis docs); 55 unique-match links added, add-only, idempotent
- SEO meta: seo_meta_vitec.json + apply_seo_meta.php fill empty seotitle/
seometa from the live vitec.com product pages (11 products, fill-up only)"
Applied on all three render paths: stdWrap.if rule on
lib.contentElementWithHeader (before the plugin copies), the same rule in
the container element definitions, and a HiddenHeaderProcessor behind the
nb content-blocks data processor. headerLayout itself stays in the payload
so the front end can tell why the header is empty.
- vitec:migrate-product-structure: exact V2 titles, Milestone typo fix (incl.
slug), QTX100 stray VSN category removed, Aligo record moved to its own
category with slug /product/aligo, new Aligo Workstation record takes over
/product/aligo-workstation
- product families (sitemap column K) are ordinary content pages now:
vitec:remove-family-records deletes the interim landing records again,
reset_subproduct_flags.php clears the misused subproduct flag (the list
renderer has always excluded subproduct=1)
- vitec:import-product-workbook: non-interactive counterpart of the module
import (same reader, mapping and DataHandler path); Arqa imported, Aligo
re-applied with the corrected capabilities markup
- ProductXlsxReader: new :copy cell selector (Body Copy with CTA fallback)
absorbs the agency's column drift; default teaser mapping uses it
- ProductListJsonRenderer: selected categories now match their whole subtree,
results ordered by category tree position, sheet order within a category
- read-only diagnostics: check_product_structure.php, check_productlist_ces.php
Kev's keyword CSV and TYPO3 hold the same solutions under slightly
different names, so the structure check filed them as missing/extra.
Matching is now slug -> normalized title -> record alias, using the
per-model alias store the import tabs got on 2026-08-18
(tx_vitec_import_mapping.record_aliases, key = lowercased CSV name).
One link therefore serves the import union list, the structure check
and vitec:create-markets (same service - linked rows are no longer
proposed as new records).
- every "Missing in TYPO3" row carries a select of all records the
direct match did not claim; "Save record links" persists per model
- alias-matched rows show under "Both" with a "linked" badge and the
same select; option 0 removes the link
- SeoResearchService::recordsCheck() additionally returns `linkable`
(the select options) and flags alias matches with via=link
- new backend POST route seo_aliases
Success stories: --markets-only repair mode
- /success-stories emitted empty `markets` for all 48 stories - a data
problem (code and TCA untouched since 17.08.); read-only diagnosis
script migrations/check_usecase_markets.php added
- vitec:import-success-stories --markets-only re-derives each story's
industries from the export exactly like the full import and rewrites
ONLY the markets MM relation of the existing record via DataHandler;
nothing else is touched, no usecase created or deleted, unresolved
stories keep their current relations
- decision: industries are translated onto the nine main markets of
the current taxonomy (INDUSTRY_TO_MARKET) instead of recreating the
seven deleted industry markets; the repair mode never creates
market records (root cause: those industry markets were deleted
after the 07.08. taxonomy import, orphaning all story relations)
- ensureMarkets() gained a $create flag for reuse by the full import
Story detail pages: real page title
- every story page answered with the generic page title "Story"; same
defect and same fix as the product pages on 21.08.: new
UsecasePageTitleProvider (singleton), registered as vitecUsecase in
config.pageTitleProviders, fed from UsecaseShowJsonRenderer with
seo_title falling back to title
Requires on the server:
vendor/bin/typo3 cache:flush
GET /search?type=7384&tx_solr[queryString]=<input> answers term
completions from the index (spell field) with document counts as lean
JSON - no page envelope, synonyms apply automatically. Two deliberate
deviations from the shipped example, which predates TYPO3 v14: the
plugin runs as USER_INT (config.no_cache is gone, and a cached USER
would pin the first query's suggestions for every later request), and
showTopResults is off (the top-result shape is hardcoded upstream with
the raw document type and full indexed content). Documented in spec
v1.12 clause 7.16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The search JSON gains tab support: the new `filter` GET parameter
restricts results to one document type (product, news, download, story,
page, market); the response carries `filter` (active value or null) and
`facets.type` with per-type counts and active flags. Counts stay
complete while a filter is active (keepAllFacetsOnSelection), so tabs
never collapse - except on an empty filtered result, documented in spec
v1.12 clause 7.16.
The parameter is named `filter` because `type` is TYPO3's reserved
page-type parameter and crashes page resolution; like q and page it is
excluded from cHash validation. Facets come from EXT:solr's native
faceting, serialized generically by SearchJsonRenderer - a future
category facet only needs TypoScript.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apache Solr 10 runs on a dedicated VPS behind a Caddy HTTPS proxy
(vitecsolr.evomedien.de) because the managed webserver only allows
outgoing standard ports. Credentials stay out of git: the site config
carries %env()% placeholders resolved via putenv() in the git-ignored
config/system/additional.php.
- composer: apache-solr-for-typo3/solr 14.0.0-RC1 (the only line
compatible with TYPO3 14; final 14.0.0 will arrive via composer update)
- config.yaml: read connection https/443, core_en per language,
env placeholder credentials; .gitignore covers additional.php
- setup.typoscript: config.index_enable = 1 (page indexing silently
refuses without it), solr_pi_results JSON renderer registration,
results highlighting, and content field extraction from tt_content
rows - the headless JSON output has no TYPO3SEARCH markers, so the
default page content extraction indexed an empty content field
- SearchJsonRenderer (renderer #27): JSON output for the search plugin
on /search. GET q/page in; { query, page, resultsPerPage, numFound,
totalPages, results[], suggestions } out. Disables the page cache per
request: config.no_cache is gone in TYPO3 v14, and q/page are
excluded from cHash, so cached variants would collide
- ext_localconf.php: q and page added to cacheHash excludedParameters
- SolrIndexCommand (vitec:solr-index): works the index queue from the
CLI with connection diagnostics and a --debug single-step mode -
EXT:solr 14 ships no console commands and the backend button indexes
one item per click
VITEC Import module: Products tab is now a searchable/sortable product
overview. "Edit Product" opens an XLSX upload with per-field source
mapping (component + cell), old/new preview and checkbox apply via
DataHandler; the mapping and manual matches are persisted and preselect
the next workbook. Category workbooks are detected and rejected.
- new: ProductXlsxReader (PhpSpreadsheet), ProductTextImportController,
Products/ProductTexts templates, 4 module routes
- related products: per-pair card text in new side table
tx_vitec_product_related_text (survives MM rewrites), emitted as
`cardtext` in the product JSON; missing MM relations added add-only
- card copy read from Body Copy (D) with fallback to CTA/Card Copy (E) -
the workbooks fill either depending on row type
- product detail page <title> now uses seotitle with title fallback
(provider made singleton and fed from the JSON renderer)
- per-user recent-search badges; last loaded workbook stored per product
(tx_vitec_product_workbook), Edit Product reopens on it
- composer: add phpoffice/phpspreadsheet ^5.9
- diagnostics: migrations/check_workbook.php
- vitec_usecaselist: layout + record selection like marketlist
- new plugin vitec_solutionlist (spec 7.13.3, key "solutions")
- tx_vitec_domain_model_usecase: detail_page -> resolved detailUrl
(also gives story cards in vitec_modelcard a link for the first time)
- "Show Toolbar" checkbox on product/market/solution/usecase lists
- spec bumped to v1.9
BREAKING: vitec_productlist and vitec_usecaselist now emit an object
instead of a bare array. Consumers must read products.products and
usecases.usecases. Also found: productlist had a configurable layout
that was never serialised; it is emitted now, but keeps its own 0-3
vocabulary instead of grid/list/carousel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes two annex items that turned out to be the same defect seen from two
sides: markets could link to a detail page, solutions could not, and the code
that turns a page uid into a URL existed four times over.
detail_page for Solution (B-11)
- ext_tables.sql, TCA (group on pages, maxitems 1, in showitem behind slug)
and Domain\Model\Solution mirror the Market field exactly
- SolutionShowJsonRenderer emits detailUrl
- ModelcardJsonRenderer serialized no detailUrl at all: its shared
market|solution branch never carried the field. Market modelcards therefore
gain a link here too, not just solutions.
Service\LinkResolver (B-12)
- one implementation replaces four private copies (Market list/show, Usecase
list/show). The copies had drifted: Market returned null on failure, Usecase
an empty string. The contract is now explicit -- null means "no link".
- Usecase keeps a thin resolvePageUrl() wrapper appending ?? '', because it
concatenates paths and a null would tear the strings apart.
- LocationsJsonRenderer and NewsJsonRenderer stay out on purpose: they resolve
a full parameter construct resp. slug paths plus canonical, which is not
page-uid-to-URL and does not fit the same contract.
Requires on the server, the column exists in code only:
vendor/bin/typo3 database:updateschema "*.add,*.change"
vendor/bin/typo3 cache:flush
New "VITEC Import" module under Web: one import page per domain model
(v1: Market, Solution, Product - registry-driven, adding a model is one
config entry). Workflow: upload a CSV (delimiter and encoding are
auto-detected, including German-Excel semicolon/Windows-1252), map CSV
columns to DB fields, persist the mapping per model together with the
identity field used for matching (new table tx_vitec_import_mapping,
no TCA - pure tool configuration), review a unified list of CSV rows
matched against the DB records (new / update with differing fields /
unchanged / db-only), then apply the checked rows through DataHandler.
Each importable row carries an editable JSON payload textarea - what
is written is the textarea content, not the raw CSV, so editors can
fix values right in the review step. The parsed CSV travels through
the form as a hidden JSON field: no session state, no temp files.
Importable fields are derived from TCA at runtime (scalar types only;
files, categories and other relations are excluded - a flat CSV
cannot carry them). Payloads are whitelisted against that field list
on apply; new records require a storage pid (prefilled from existing
records). BE user permissions apply via DataHandler.
The module ships its own CSS (backend-import.css, loaded only by
this module) using the frontend button palette from _vitec.scss:
orange #f47937 for primary actions, navy #26358c for secondary
actions and structure. The stray <h2>Hi</h2> debug leftover in the
shared backend layout is removed (also affects the OG Image module).
A fourth tab "SEO Research" handles the recurring keyword-research
CSV. It is deliberately not an import mask - the file carries research
only (no meta title/description yet). Each upload is persisted as a
delivery (tx_vitec_seo_research, never deleted) and evaluated: diff
against the previous delivery keyed by URL, a structure check of the
CSV tree against TYPO3 (pages by slug path; market/solution/product
rows against the domain tables, matched by slug then normalized
title), and the three work lists from the SEO flags (quick wins by
GSC impressions, shared terms grouped by keyword, already ranking).
CsvReader now deduplicates repeated header names, which that CSV has.
New CLI command vitec:create-markets: creates the market records the
structure check reports as missing, sourced from the stored delivery
and matched through the same SeoResearchService - what the module
lists is what the command creates. Each market gets a sys_category of
the same title, found anywhere under the auto-detected market category
root or created; sub-market categories are created under the parent
market's category, so the category tree carries the hierarchy the
flat market model cannot. Idempotent, dry-run first.
New CLI commands vitec:create-markets and vitec:market-dummy-image.
create-markets creates the market records the structure check reports
as missing (root detection three-staged: option, auto-detect, find or
create a "Markets" category). market-dummy-image assigns a shared
placeholder (white logo on brand navy, fileadmin/placeholders/) to
every market without an image - one sys_file for all, replacing the
file restyles every placeholder at once. Both idempotent.
Deliberately out of v1: import log with three-way compare (protection
against overwriting manual edits), images/relations, multiple saved
mappings per model.
New "VITEC Import" module under Web: one import page per domain model
(v1: Market, Solution, Product - registry-driven, adding a model is one
config entry). Workflow: upload a CSV (delimiter and encoding are
auto-detected, including German-Excel semicolon/Windows-1252), map CSV
columns to DB fields, persist the mapping per model together with the
identity field used for matching (new table tx_vitec_import_mapping,
no TCA - pure tool configuration), review a unified list of CSV rows
matched against the DB records (new / update with differing fields /
unchanged / db-only), then apply the checked rows through DataHandler.
Each importable row carries an editable JSON payload textarea - what
is written is the textarea content, not the raw CSV, so editors can
fix values right in the review step. The parsed CSV travels through
the form as a hidden JSON field: no session state, no temp files.
Importable fields are derived from TCA at runtime (scalar types only;
files, categories and other relations are excluded - a flat CSV
cannot carry them). Payloads are whitelisted against that field list
on apply; new records require a storage pid (prefilled from existing
records). BE user permissions apply via DataHandler.
The module ships its own CSS (backend-import.css, loaded only by
this module) using the frontend button palette from _vitec.scss:
orange #f47937 for primary actions, navy #26358c for secondary
actions and structure. The stray <h2>Hi</h2> debug leftover in the
shared backend layout is removed (also affects the OG Image module).
Deliberately out of v1: import log with three-way compare (protection
against overwriting manual edits), images/relations, multiple saved
mappings per model.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New "VITEC Import" module under Web: one import page per domain model
(v1: Market, Solution, Product - registry-driven, adding a model is one
config entry). Workflow: upload a CSV (delimiter and encoding are
auto-detected, including German-Excel semicolon/Windows-1252), map CSV
columns to DB fields, persist the mapping per model together with the
identity field used for matching (new table tx_vitec_import_mapping,
no TCA - pure tool configuration), review a unified list of CSV rows
matched against the DB records (new / update with differing fields /
unchanged / db-only), then apply the checked rows through DataHandler.
Each importable row carries an editable JSON payload textarea - what
is written is the textarea content, not the raw CSV, so editors can
fix values right in the review step. The parsed CSV travels through
the form as a hidden JSON field: no session state, no temp files.
Importable fields are derived from TCA at runtime (scalar types only;
files, categories and other relations are excluded - a flat CSV
cannot carry them). Payloads are whitelisted against that field list
on apply; new records require a storage pid (prefilled from existing
records). BE user permissions apply via DataHandler.
Deliberately out of v1: import log with three-way compare (protection
against overwriting manual edits), images/relations, multiple saved
mappings per model.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New CLI command vitec:import-downloads: reads the JSON export of the
old site (www.vitec.com/import), cuts it out of the surrounding page
template by brace counting, and imports the downloads idempotently by
slug. Scope is deliberately narrow: only files under
/fileadmin/downloads/Collateral/, records without title or file are
skipped, records sharing one file_url are merged. Files are fetched
resumably into fileadmin/downloads/Collateral/ and stored on pid 29.
Legacy filenames are normalized on fetch so every imported file
matches the <prefix>__<filetype>__<NN>-<letter> version convention:
__NN_A -> __NN-A, ___NN -> __NN, __NNA -> __NN-A, and a bare __NN
gets -A appended as its initial revision. Verified against the full
export: all 179 names match afterwards, none of the already-correct
ones change.
tx_vitec_domain_model_download gains a second category field "type"
(display taxonomy: Datasheet, Success Story, Brochure, Software) next
to the filename-driven filetype categories. No SQL change needed -
the core generates columns for category fields. The three download
renderers emit the field as a string analogous to filetype; MM rows
are distinguished by fieldname, existing queries filter on it and
remain unaffected.
Import values are matched via the custom columns sys_category.filetype
and .type (falling back to title); missing categories are created
(filetype under --category-parent, default 4; type under a "Download
Type" parent).
getDownloadFile() in the three download renderers gains a filepath
fallback (FAL -> convention -> filepath) as a safety net for any
future filename that escapes the convention - previously such records
emitted file: null.
Architecture spec bumped to v1.7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>