Skip to content

Commit 71edf34

Browse files
author
Sebastian BURGIN-FIX (ext)
committed
wip
1 parent 3cbfe82 commit 71edf34

33 files changed

Lines changed: 734 additions & 199 deletions

GAP_ANALYSIS.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Gap Analysis v2: `feature-updates` (master) vs. `main` and `feature-refactor` (PR #48)
2+
3+
**Date:** 2026-07-22
4+
**Framing:** `feature-updates` (current branch, → `production`) is treated as the reference/master. This document tracks where **`main`** and **`feature-refactor`** contain functionality or content that master does **not** have.
5+
6+
## Headline finding
7+
8+
This reframing surfaced something bigger than the original PR #48 comparison: **`main` is not behind `feature-updates` — the two branches split into two different rewrites of the same site**, and `main` went much further architecturally. A single large commit on `main` (`20ff73c`, 2026-06-01, "wip") replaced the entire DB-seeded content model with a flat-file `content/` system, deleted ~10 legacy Eloquent models, added a much larger UI kit, custom error pages, CSP/security hardening, sitemap generation, and an internal "coding-guidelines" package — none of which ever made it into `feature-updates`. At the same time, `feature-updates` has 68 commits of its own (AI/llms.txt feature, dependabot bumps, various fixes) that `main` doesn't have.
9+
10+
In other words: neither branch is a strict superset of the other. If `production` (built on `feature-updates`) becomes the sole surviving line, **you would be shipping backwards** on everything in the table below unless it's deliberately re-integrated.
11+
12+
`feature-refactor` (PR #48), by contrast, really is the smaller/older of the three — it forked earlier and was mostly superseded, with the exceptions noted in §3.
13+
14+
---
15+
16+
## 1. Branch relationship (current tri-graph)
17+
18+
| Comparison | Commits only in left | Commits only in right |
19+
|---|---|---|
20+
| `main``feature-updates` | **52** (incl. the big `20ff73c` rewrite) | **68** |
21+
| `main``feature-refactor` | forked earlier (`bc47f78`), mostly superseded | 34 |
22+
| `feature-updates``production` | 0 | 20 (production is behind, PR #76 open to catch it up) |
23+
24+
All three (`main`, `feature-refactor`, `feature-updates`) share the same common ancestor for the relevant comparison (`e3ef885`), except `feature-refactor` which forked slightly earlier (`bc47f78`).
25+
26+
---
27+
28+
## 2. Gap table: `main``feature-updates` (master)
29+
30+
Everything below exists on `main` and is **absent** from `feature-updates`/`production` right now.
31+
32+
| Area | On `main` | On master (`feature-updates`) | Gap severity |
33+
|---|---|---|---|
34+
| **Legal pages enabled** | `TermsIndexController` and `JobsIndexController` render real views (no redirect-home) | Both still `redirect()` to homepage behind commented-out code (`// @todo Notification`) | 🔴 High — main already fixed exactly the two disabled pages flagged in the original analysis |
35+
| **Content architecture** | Flat-file `content/{news,products,services,open-source,technologies,co-working}/{locale}/*.md` + `App\Content\MarkdownContentService` / `ContentItem` | Still DB-seeded via `database/seeders/Codebar/*TableSeeder.php` + Eloquent models | 🔴 High — fundamentally different content pipeline; merging later means reconciling two content sources |
36+
| **Legacy model layer** | `Configuration`, `Contact`, `News`, `OpenSource`, `Page`, `Product`, `ProductModule`, `Reference`, `Role`, `Service`, `Technology`, `User` models **all deleted**, along with their factories, policies, and Nova/permission config | All still present and in active use | 🔴 High — this is the crux of the divergence; any merge will be a real migration project, not a fast-forward |
37+
| **Co-working feature** | New `CoWorkingIndexController` + `content/co-working/{de_CH,en_CH}/page.md` + view | Does not exist at all | 🟠 Medium — a real, shippable new business offering/page missing from master |
38+
| **`config/site.php`** | Rich config: real office addresses (Zunzgen HQ + Oberwil branch, with lat/lng + Maps links), contact email/phone, per-section feature flags, and full bilingual "Who we are / How we work" intro copy | No `config/site.php`; office addresses are hardcoded directly in `resources/views/app/contact/index.blade.php` (content itself matches, just not centralized/data-driven) | 🟢 Low — same content, different (less flexible) implementation; not a content loss |
39+
| **UI component kit** | Much larger `resources/views/components/ui/*` (button, card, hero, hero-home, feature-card, feature-row, logo-cloud, nav-link, quote, tag, footer-column, footer-link, section-header, divider, mono, lead…) + `blocks/*` (contact-cta, pricing-card, timeline, plus the ones also seen in `feature-design`) | Its own smaller, independently-built `ui/*` set (panel, row, link, prose, button, badge, badge-link) | 🟡 Low–Medium — two incompatible design systems now exist; worth a design-system reconciliation decision, not an urgent content loss |
40+
| **Custom error pages** | Full set: `401,402,403,404,419,429,4xx,500,503,5xx` + partials (`_auth-error`, `_http-error-client/server`, `_simple`) | No custom error views — falls back to Laravel/browser defaults | 🟠 Medium — real UX/branding gap on error states (404, 500, maintenance, rate-limit) |
41+
| **Security/CSP hardening** | `app/Security/SecurityPolicyBasic.php`, `app/Support/CspAllowlist.php`, `config/csp-allowlists.json`, expanded `config/csp.php`, `config/feature-policy.php` updates | `config/csp.php` exists but without the allowlist/security-policy layer | 🟠 Medium — a security-relevant hardening pass that didn't carry over |
42+
| **Sitemap generation** | New `GenerateSitemapCommand`, updated `SitemapBuilder`/`SitemapController`, `SitemapControllerTest` removed in favor of new test | `SitemapController`/`SitemapBuilder` exist but not the generator command; old `SitemapControllerTest` still present | 🟡 Low–Medium — worth checking if sitemap output is now stale relative to the new content model |
43+
| **`packages/coding-guidelines`** | A full internal package: Laravel Boost-style `SKILL.md` files (blade, controllers, models, testing, docuware, etc.), an `AGENTS.md` for AI-assisted coding, a `ValidateSkillsCommand`, and tests for it | Nothing — no `AGENTS.md`, no `boost.json`, no skills package | 🟢 Low (dev-tooling, not customer-facing) — but directly useful if you work on this repo with Claude Code/Boost; worth pulling over independent of the content-model question |
44+
| **Observability/ops config** | `config/nightwatch.php` (Laravel Nightwatch), `config/history.php`, `config/pillars.php`, `config/team.php`, `config/content.php` | None of these exist | 🟡 Low–Medium — depends whether these tools are actually wired to a live service; verify before assuming they're "just config" |
45+
| **Honeypot spam protection view** | `resources/views/vendor/honeypot/honeypotFormFields.blade.php` published | `config/honeypot.php` exists on master already, but no published view override | 🟢 Low |
46+
| **Test suite renames/cleanup** | `phpunit.xml.dist``phpunit.xml`, `tests/Core/ClassnamesTest.php`, various renamed Enum tests, removed tests for deleted models | Old naming/structure retained (matches its still-present legacy models) | 🟢 Low — consequence of the model deletion, not independently actionable |
47+
48+
---
49+
50+
## 3. Gap table: `feature-refactor` (PR #48) → `feature-updates` (master)
51+
52+
Narrower — most of PR #48 is superseded. What's still uniquely present there and missing from master:
53+
54+
| Area | On `feature-refactor` | On master | Gap severity |
55+
|---|---|---|---|
56+
| **Terms & Conditions content** | Full 188-line German AGB draft in `database/files/legal/de_CH/terms.md`, plus `terms_feedback.md` with unresolved stakeholder review notes | `terms.md` empty (0 lines); page redirects home | 🔴 High — **note:** `main` independently re-enabled the Terms *page*, but `main` did **not** carry this drafted AGB content (its version of `terms.md` was deleted outright as part of the content-model migration). This draft + feedback currently exists **only** on `feature-refactor` and nowhere else. |
57+
| **Opening-hours contact widget** | `ContactIndexController` passes structured weekly hours; new `<x-opening-hours>` component with live open/closed indicator | No such data or component | 🟠 Medium — small, self-contained, easy to port |
58+
| **GKI consulting service copy** | `GkiServiceData` + `Ai{Index,Show}Controller` — "GKI Strategy/Sprint/Build" consulting packages with real German marketing copy | No equivalent copy anywhere (current AI section is about llms.txt generation, a different feature) | 🟡 Low–Medium — confirm with business side whether this offering is still wanted |
59+
| **`SyncRepositoriesCommand`** | Automated GitHub org repo sync (stars/forks/downloads via API + Packagist) into the open-source listing | `OpenSourceTableSeeder` is fully hand-maintained | 🟢 Low — ops convenience, not content |
60+
| Everything else (nav component extraction, Matrix well-known, `OpenSource``GithubRepository` rename, `config/site.php` v1) || Already equivalent or superseded on master | ✅ No gap |
61+
62+
---
63+
64+
## 4. What master has that neither `main` nor `feature-refactor` have
65+
66+
For completeness — these are *not* gaps, but confirm `feature-updates` isn't simply "behind":
67+
68+
- The AI/llms.txt feature (`AiLlmIndexController`, `AiLlmAnalyticsIndexController`, `llm:fetch-analytics` scheduled command) — merged from `feauter-llms`, exists only on master/production.
69+
- 68 commits of dependabot bumps, CI fixes, and WIP work not present on `main`.
70+
71+
---
72+
73+
## 5. Recommendation
74+
75+
Given `main` contains an architecture rewrite this substantial, **the real decision isn't "can we delete `feature-refactor`/`feature-design`"** (yes, safely, modulo §3) — **it's whether `main`'s `20ff73c` rewrite is meant to become the site's actual direction, and if so, how/whether it reconciles with `feature-updates`/`production`.** Concretely:
76+
77+
1. Confirm with whoever ran the `20ff73c` migration on `main` (git blame says the same author) whether it's a live intent (new architecture going forward) or an abandoned experiment — the diff is too large and too central (deletes core models) to assume either way.
78+
2. If it's the intended direction: treat merging `main` into `feature-updates` as its own planned migration project (content-model change, not a simple merge), and pull the Terms AGB draft + feedback from `feature-refactor` into it while you're at it, since `main`'s rewrite deleted the old `terms.md` without carrying that draft forward.
79+
3. If `main`'s rewrite is not going forward: it still contains real, isolated wins worth cherry-picking regardless of the content-model question — custom error pages, CSP/security hardening, the co-working page, and the `packages/coding-guidelines`/`AGENTS.md` dev-tooling package.
80+
4. Either way, port the opening-hours widget from `feature-refactor` — it's small and uncontroversial.
81+
5. Once the Terms content and opening-hours widget are ported (or consciously dropped), `feature-refactor` can be deleted. `feature-design` was already self-archived by its own commit message and is safe to delete now. Resolve `main` vs. `feature-updates` as a separate, larger workstream before touching `main`.

app/Actions/PageAction.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public function default(): ?PageDTO
4141
routeKey: $page->key,
4242
routeName: Str::slug($page->locale).'.'.$this->routeName,
4343
title: $page->title,
44+
robots: $page->robots,
4445
description: $page->description,
4546
image: $page->image,
4647
lastModificationDate: Carbon::parse($page->updated_at ?? now()),
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\CoWorking;
4+
5+
use App\Actions\PageAction;
6+
use App\Http\Controllers\Controller;
7+
use Illuminate\View\View;
8+
9+
class CoWorkingIndexController extends Controller
10+
{
11+
public function __invoke(): View
12+
{
13+
return view('app.co-working.index')->with([
14+
'page' => (new PageAction(locale: null, routeName: 'co-working.index'))->default(),
15+
'services' => $this->services(),
16+
'pricing' => [
17+
'name' => __('Single workstation'),
18+
'price_chf' => 750,
19+
'period' => __('month'),
20+
],
21+
'optionalServices' => [
22+
['name' => __('Monitor & video camera'), 'price' => __('On request')],
23+
['name' => __('Parking with EV charging'), 'price' => __('from CHF 250.00 / month')],
24+
['name' => __('Fibre upgrade up to 5 Gbit/s'), 'price' => __('On request')],
25+
['name' => __('Static public IP'), 'price' => __('On request')],
26+
],
27+
'rentalConditions' => [
28+
'minimum_months' => 12,
29+
'notice_months' => 3,
30+
'deposit_text' => __('3 months\' rent per workstation'),
31+
],
32+
]);
33+
}
34+
35+
private function services(): array
36+
{
37+
return [
38+
['category' => __('Workplace'), 'title' => __('Height-adjustable desk'), 'teaser' => __('LO-Next system by Lista Office AG, plus storage for personal materials.')],
39+
['category' => __('Workplace'), 'title' => __('Ergonomic chair'), 'teaser' => __('Wilkhahn seating designed for long, healthy workdays.')],
40+
['category' => __('Workplace'), 'title' => __('Meeting pod'), 'teaser' => __('Framery One pod for calls and small meetings.')],
41+
['category' => __('Workplace'), 'title' => __('Meeting room with 50″ monitor'), 'teaser' => __('Includes video-call infrastructure for team meetings.')],
42+
['category' => __('IT & connectivity'), 'title' => __('250 Mbit/s private network'), 'teaser' => __('With 5G failover for uninterrupted connectivity.')],
43+
['category' => __('IT & connectivity'), 'title' => __('Managed enterprise infrastructure'), 'teaser' => __('Integrated into a professionally managed enterprise infrastructure.')],
44+
['category' => __('IT & connectivity'), 'title' => __('Printer & scanner'), 'teaser' => __('Professional, available to all members.')],
45+
['category' => __('Building'), 'title' => __('24/7 access'), 'teaser' => __('Keyless entry via RFID & app — no physical keys.')],
46+
['category' => __('Building'), 'title' => __('Business address & mailbox'), 'teaser' => __('Use the address as your registered company location.')],
47+
['category' => __('Building'), 'title' => __('Fully-equipped kitchen'), 'teaser' => __('Including coffee and a Quooker water dispenser.')],
48+
['category' => __('Building'), 'title' => __('Weekly professional cleaning'), 'teaser' => __('Office, kitchen and sanitary areas — every Wednesday morning.')],
49+
['category' => __('Services'), 'title' => __('On-site IT support'), 'teaser' => __('A direct line to the on-site tech team — one hour of consulting per month included.')],
50+
];
51+
}
52+
}

app/Http/Controllers/Contact/ContactIndexController.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ public function __invoke(): View
1515
{
1616
return view('app.contact.index')->with([
1717
'page' => (new PageAction(locale: null, routeName: 'contact.index'))->default(),
18+
'openingHours' => $this->openingHours(),
1819
]);
1920
}
21+
22+
private function openingHours(): array
23+
{
24+
return [
25+
['day' => 'Monday', 'open' => '08:00', 'close' => '18:00'],
26+
['day' => 'Tuesday', 'open' => '08:00', 'close' => '18:00'],
27+
['day' => 'Wednesday', 'open' => '08:00', 'close' => '18:00'],
28+
['day' => 'Thursday', 'open' => '08:00', 'close' => '18:00'],
29+
['day' => 'Friday', 'open' => '08:00', 'close' => '18:00'],
30+
['day' => 'Saturday', 'open' => '08:00', 'close' => '18:00'],
31+
['day' => 'Sunday', 'open' => null, 'close' => null],
32+
];
33+
}
2034
}

app/Http/Controllers/Network/NetworkManageUpdateController.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ class NetworkManageUpdateController extends Controller
1919
public function __invoke(NetworkManageUpdateRequest $request, NetworkUser $networkUser): RedirectResponse
2020
{
2121
// Hard whitelist: a signed link may only ever touch the person's own
22-
// contact channels, avatar, their own visibility and the company website.
22+
// name, contact channels, avatar, their own visibility and the
23+
// company website. Email is deliberately excluded — only codebar can
24+
// change it, so it's never read from the request even if submitted.
2325
$attributes = [
24-
'email' => $request->validated('email'),
26+
'name' => $request->validated('name'),
2527
'linkedin' => $request->validated('linkedin'),
2628
'phone' => $request->validated('phone'),
2729
'published' => $request->boolean('published'),

app/Http/Controllers/Network/NetworkRequestStoreController.php

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,17 @@
44

55
use App\Http\Controllers\Controller;
66
use App\Http\Requests\Network\NetworkRequestStoreRequest;
7-
use App\Jobs\Mail\NetworkManageLinkMail;
8-
use App\Models\NetworkUser;
7+
use App\Jobs\Network\SendNetworkManageLinkJob;
98
use Illuminate\Http\RedirectResponse;
10-
use Illuminate\Support\Facades\Mail;
11-
use Illuminate\Support\Facades\URL;
12-
use Illuminate\Support\Str;
139

1410
class NetworkRequestStoreController extends Controller
1511
{
1612
public function __invoke(NetworkRequestStoreRequest $request): RedirectResponse
1713
{
18-
$networkUser = NetworkUser::query()
19-
->where('email', $request->validated('email'))
20-
->first();
21-
22-
if ($networkUser) {
23-
$url = URL::temporarySignedRoute(
24-
Str::slug(app()->getLocale()).'.network.manage.show',
25-
now()->addHours(48),
26-
['networkUser' => $networkUser],
27-
);
28-
29-
Mail::to($networkUser->email)->send(new NetworkManageLinkMail($networkUser, $url));
30-
}
14+
SendNetworkManageLinkJob::dispatch(
15+
$request->string('email')->value(),
16+
app()->getLocale(),
17+
);
3118

3219
// Always the same response, so email addresses cannot be enumerated.
3320
return redirect()

app/Http/Requests/Network/NetworkManageUpdateRequest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ public function authorize(): bool
1818
public function rules(): array
1919
{
2020
return [
21-
'email' => ['nullable', 'email', 'max:255'],
21+
'name' => ['required', 'string', 'max:255'],
2222
'linkedin' => ['nullable', 'url', 'max:255'],
2323
'phone' => ['nullable', 'string', 'max:50'],
2424
'website' => ['nullable', 'url', 'max:255'],
2525
'published' => ['nullable', 'boolean'],
26-
'avatar' => ['nullable', 'image', 'mimes:jpeg,png,webp', 'max:2048'],
26+
// "image" isn't used here — its hardcoded mime whitelist has no avif entry,
27+
// so it would reject avif uploads even though the "mimes" rule below allows them.
28+
'avatar' => ['nullable', 'file', 'mimes:jpeg,png,webp,avif', 'max:2048', 'dimensions:ratio=1/1'],
2729
];
2830
}
2931
}

app/Jobs/Mail/NetworkManageLinkMail.php

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)