Commit Graph

3338 Commits

Author SHA1 Message Date
Nabeel Shahzad
4bbc94faec
fix(routeforge): address PR review comments
- Add messages() to CommitRequest and PreviewAirportsRequest with new
  validation translation keys (fare_multiplier_format, on_conflict_invalid,
  near_invalid, max_range_nm_invalid).
- DaysPicker: read form.value inside toggle/selectAll/clearAll handlers
  to avoid clobbering concurrent edits with a stale snapshot.
- AirportPicker: track showingUnfiltered so clearing a typed query
  refetches the alpha-sorted initial page instead of leaving stale
  filtered results in the dropdown.
2026-05-27 16:07:49 -05:00
Nabeel Shahzad
7489d9dcda
More optimizations and adjustments 2026-05-27 14:26:29 -05:00
Nabeel Shahzad
bece5ca0d5
Update translations/label mappings 2026-05-26 18:25:05 -05:00
Nabeel Shahzad
6ffe7f344b
refactor(routeforge): lint subsystem cleanup
Internal refactor preserving wire shape:
- LintSeverity backed enum replaces SEVERITY_* string constants;
  LintReport::fromIssues match is exhaustive
- LintRow value object replaces array<string, mixed> rows
- Per-rule public const ID + SEVERITY; LintRule interface keeps
  only check(LintContext)
- routeforge.lint_rules container tag replaces LintRunner::defaults();
  LintRunner non-final + non-readonly (mockable)
- batchPayload() test helper encapsulates on_conflict knob

Wire shape preserved: LintIssue::toArray() emits severity->value so
/lint and /commit responses stay byte-identical.

OpenSpec: archive/2026-05-26-routeforge-lint-cleanup
Tests: 25/25 lint filter, 171/171 RouteForge filter
2026-05-26 16:59:41 -05:00
Nabeel Shahzad
3c7f0e3934
feat(routeforge): refine duplicate detection
Bundles three completed OpenSpec changes:

routeforge-duplicate-detection-refinement:
- StrictDuplicateKey value object owns the 5-tuple (bundle_id,
  airline_id, flight_number, route_code, route_leg). L4 / L5 / L12 /
  DuplicateChecker delegate; no more dup'd dupKey() / normalize().
- L5 severity flipped WARNING -> ERROR for same-bundle full-tuple
  matches. Scoped to enabled = true AND owner_type IS NULL AND same
  bundle_id.
- L12 new WARNING for cross-bundle airline+flight collisions.
  Catalog grows 12 -> 13 rules.
- DuplicateChecker (the typing-time /check-duplicates endpoint)
  classifies results as same_bundle / error vs cross_bundle / warning,
  passes existing_bundle_id + existing_bundle_name to the UI.
- New migration adds flights._dup_key generated column + UNIQUE
  index. STORED on MySQL / MariaDB / PostgreSQL; VIRTUAL on SQLite
  (ALTER TABLE quirk). Driver-switched expression.
- Migration first canonicalizes route_code / route_leg storage
  ('' / '0' / 0 -> NULL), then auto-disables pre-existing dup rows
  via a single window-function pass (lowest id per cluster kept),
  with activity-log entries per disabled row.
- Flight model gains routeCode() / routeLeg() Attribute mutators
  for canonical NULL on set. Legacy 'route_leg' => 'integer' cast
  removed.
- README MySQL min bumped 5.7 -> 8.0 (window functions needed
  by the dedup migration).
- Frontend types updated: DuplicateMatch now carries severity,
  kind, existing_bundle_id, existing_bundle_name.

routeforge-page-boot-via-api:
- /boot endpoint + RouteForgeBootResource serving SPA bootstrap
  envelope (CSRF, user, airlines, routes, config, translations).
- Frontend boot.ts state init, BootError component.

routeforge-controller-thin:
- Extract AirlineStatsService, CommitInputFactory,
  LintContextFactory from the controller.
- New BundlesRequest + RouteForgeBundleResource.

Misc cleanup:
- Support\Geo utility extracted from generator.ts logic.
- BaseRouteForgeBatchRequest validation polish.

Verified: 670 pest tests pass (2682 assertions). PHPStan clean on
touched paths. Pint clean (--dirty). OpenSpec validate --strict
green.
2026-05-26 10:19:33 -05:00
Nabeel Shahzad
337e4ab79d
refactor(routeforge): bulk-insert commit pipeline
Replace per-row Flight::create() loop in RouteForgeService::commit with
single multi-row Flight::insert + DB::table flight_subfleet/flight_fare
bulk inserts. 100-row commit drops from ~700 queries to <=5.

Drop Flight::withoutEvents wrapper: query-builder bulk insert bypasses
Eloquent events natively, so no per-flight LogsActivity spam.

Move ICAO uppercase/trim from App\Observers\FlightObserver to Flight
model dpt_airport_id/arr_airport_id Attribute mutators; delete the
observer. Single-row write paths flow through the mutators; bulk-insert
path normalizes explicitly via Flight::fill + getAttributes so casts +
mutators run uniformly per row.

Drop synchronous SetVisibleFlights::runForBundle post-commit. Visibility
settles via the queued RecomputeBundleVisibility job: BundleObserver
::created handles create-new mode, RouteForgeService dispatches
explicitly in attach-existing mode (observer does not fire when no new
bundle is persisted).

New tests: ICAO mutator unit coverage, bulk-insert query-count budget,
Queue::fake() dispatch assertions for both bundle modes.

Refs OpenSpec change routeforge-commit-bulk-insert.
2026-05-25 13:57:32 -05:00
Nabeel Shahzad
712b2956d6
fix redirect url after flight creation 2026-05-24 23:23:19 -05:00
Nabeel Shahzad
1fe857c4e3
Rename 'chain' to 'tour', update row format/stickyness and other big fixes 2026-05-24 23:15:32 -05:00
Nabeel Shahzad
9456ae39ed
Add help modal 2026-05-24 12:58:37 -05:00
Nabeel Shahzad
beb5415c2f
Fixes from PR feedback 2026-05-24 12:34:35 -05:00
Nabeel Shahzad
bc74673fae
Update pkg lock 2026-05-24 11:56:12 -05:00
Nabeel Shahzad
1f9f5cc32a
test(routeforge): pest + vitest coverage + service fixes
Add 27 PHP test files + 6 TS test files + Vitest setup, surfacing
and fixing 4 production bugs in the process.

Test counts (all green):
- PHP: 545 tests / 2341 assertions / 25.25s
  - 12 lint rule tests (L1, L2, L2b, L3, L4, L5, L6, L7, L8, L9, L10, L11)
  - LintRunner dispatch + LintReport bucket aggregation
  - DuplicateChecker bulk-query + N+1 protection
  - RouteForgeService full commit pipeline (happy path, lint rollback,
    attach-existing branch, fare multiplier, withoutEvents suppression)
  - 6 endpoint feature tests (PreviewAirports, Subfleets, AirlineStats,
    CheckDuplicates, Lint, Commit)
- TS: 46 tests / 6 files / 280ms via Vitest 4.1.7 + happy-dom
  - geo, timezone, flightNumber, timeStrategy, generator, lint

In-scope production fixes discovered by writing the tests:

1. DuplicateChecker eager-load was 'airline:id,code' but 'code' is an
   accessor over iata/icao columns. Loaded airline came back with
   NEITHER backing column populated and Flight::ident dropped the
   airline prefix in the duplicates response. Changed to
   'airline:id,iata,icao'.

2. LintRunner constructor takes 'array $rules' with no container-
   resolvable shape. Both /lint and /commit endpoints would have
   failed at runtime with 'Unresolvable dependency'. Registered as
   singleton in AppServiceProvider::register() binding to
   LintRunner::defaults().

3. BaseRouteForgeBatchRequest's 'subfleet_ids' rule was
   ['required', 'array', 'min:0']. 'required' rejects empty arrays
   in Laravel, contradicting 'min:0'. Relaxed to ['present', 'array'];
   L3 lint catches the empty case as a warning downstream.

4. RouteForgeService::commit() called auth()->user() directly,
   violating tests/Arch/GlobalTest's http-helpers rule (auth/session/
   request only allowed in App\Http / App\Filament / App\Livewire /
   App\Providers\Filament). Added $causerId field to CommitInput;
   controller stamps it from auth()->id(); service resolves User by id.

Infrastructure:
- tests/Support/RouteForgeTestHelpers.php — shared LintContext + row +
  airport + batchPayload builders. PSR-4 autoloaded via Tests\ namespace;
  static class avoids global-function name collisions across the 15+
  RouteForge test files.
- vitest.config.ts at project root, scoped to
  resources/js/admin/routeforge/**/*.test.ts. happy-dom provides the
  minimal window + DOM globals lib/i18n.ts depends on.
- package.json gains 'test': 'vitest run' script.
- Added vitest, happy-dom, @types/node as devDependencies.

Task 4.6.3 stays N/A — FlightNumberAssigner was removed per the
Section 4 banner; no PHP equivalent to test. Section 6.4.7's planned
PHP↔TS shared fixtures (tests/fixtures/routeforge/generator/) also
N/A for the same reason; TS generator tests stand alone.

Quality gates green:
- tsc --noEmit: clean
- oxlint (40 files / 93 rules): 0 warnings, 0 errors
- oxfmt --check: all formatted
- npm run build: routeforge bundle stays at 26.69 KB gzip
  (Decision 16 budget 60 KB → 33.31 KB headroom)
- vendor/bin/pint --dirty: passed
- vendor/bin/phpstan analyse: OK no errors
- vendor/bin/rector --dry-run: clean for RouteForge files
2026-05-24 11:55:36 -05:00
Nabeel Shahzad
2c7f4f5cc9
chore(routeforge): rector NewlineAfterStatement fixes
Two cosmetic blank-line-after-continue fixes flagged by
'vendor/bin/rector --dry-run' on the route-forge change:

- app/Services/RouteForge/Rules/L6OriginEqualsDestination.php
- app/Http/Controllers/Admin/RouteForgeController.php (decorate loop)

Section 9 quality gates green:
- tsc strict: clean
- oxlint (34 files / 93 rules): 0 warnings, 0 errors
- oxfmt: all formatted
- vite build: 113 modules, routeforge entry 26.69 KB gzip
  (Decision 16 budget 60 KB -> 33.31 KB headroom)
- pint --dirty: passed
- phpstan level 5: no errors
- rector dry-run scoped to these 2 files: clean post-fix

Out-of-scope rector findings (19 legacy Laravel-13 attribute migrations
across app/Models/*.php and app/Console/Commands/*.php) shipped on a
separate branch (rector-modernize-legacy).
2026-05-24 11:55:36 -05:00
Nabeel Shahzad
370c2f6fdf
feat(routeforge): admin batch flight composer + lint pipeline
New Filament page at /admin/route-forge: compose dozens of scheduled
flights across five topologies (Hub→Spokes, Spokes→Hub, Hub & Spokes,
Mesh, Chain), preview rows live, lint for operational issues, commit
as one Flight Bundle in a single atomic transaction.

Frontend: TypeScript + Preact 10 + @preact/signals + @date-fns/tz.
Separate Vite entry, 26.69 KB gzip (well under the 60 KB target).
Client-side generator + 12 lint rules; localStorage draft resume.

Backend: RouteForge service + 12 PHP lint rule classes + duplicate
checker + commit orchestration (Flight::withoutEvents + single
bundle-level activity log). Six endpoints under
/admin/route-forge/api/, gated by permission:create:flight.

Bundle picker is dual-mode — type a new name to create, or pick an
existing bundle to append flights to (read-only summary, server skips
the bundle persist step and stamps the existing id onto new flights).

Airport picker: click-to-open dropdown with 50-result alpha pages,
checkbox rows for multi-select, prefix-only search via new
searchMode=prefix opt-in on the shared AirportSearchQueryV1
(/api/airports default substring behavior preserved).

Quality: tsc strict mode clean, oxlint 0/0, phpstan no errors,
pint passing. Pest scaffolding deferred per workflow.

Tracking: openspec/changes/route-forge — 66/106 tasks complete
(sections 1, 2, 4, 5, 6, 7, 8; Pest 4.6/5.3/6.4 + sections 9–12
remaining).

# Conflicts:
#	package-lock.json
#	package.json
2026-05-24 11:55:33 -05:00
Nabeel S.
92ad8f36c6
refactor: migrate to Laravel 13 attribute syntax (#2220)
Apply rector's Laravel13 set:

- 12 models: $incrementing/$timestamps properties ->
#[WithoutIncrementing] / #[WithoutTimestamps] attributes
- 7 console commands: $signature property -> #[Signature(...)] attribute

Mechanical changes via 'vendor/bin/rector process'. No behavior changes.
All gates green: pint, phpstan, rector dry-run clean post-fix.

Out-of-scope to the route-forge change; landed separately so the
RouteForge PR diff stays focused on the new feature.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Migrated console command declarations to modern PHP attribute-based
signatures for improved code organization.
* Updated application models to use modern PHP attributes for
configuration instead of traditional properties while preserving all
existing functionality.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2220?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-24 10:58:31 -05:00
Nabeel S.
ec788bf3b3
Merge branch 'main' into rector-modernize-legacy 2026-05-24 10:55:13 -05:00
Nabeel Shahzad
7cb6056234
refactor: migrate to Laravel 13 attribute syntax
Apply rector's Laravel13 set:

- 12 models: $incrementing/$timestamps properties -> #[WithoutIncrementing] / #[WithoutTimestamps] attributes
- 7 console commands: $signature property -> #[Signature(...)] attribute

Mechanical changes via 'vendor/bin/rector process'. No behavior changes.
All gates green: pint, phpstan, rector dry-run clean post-fix.

Out-of-scope to the route-forge change; landed separately so the
RouteForge PR diff stays focused on the new feature.
2026-05-24 10:40:26 -05:00
Nabeel S.
15a8f1e2d7
build(deps): bump axios from 1.15.0 to 1.15.2 (#2206)
Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/releases">axios's
releases</a>.</em></p>
<blockquote>
<h2>v1.15.2</h2>
<p>This release delivers prototype-pollution hardening for the Node HTTP
adapter, adds an opt-in <code>allowedSocketPaths</code> allowlist to
mitigate SSRF via Unix domain sockets, fixes a keep-alive socket memory
leak, and ships supply-chain hardening across CI and security docs.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Prototype Pollution Hardening (HTTP Adapter):</strong>
Hardened the Node HTTP adapter and
<code>resolveConfig</code>/<code>mergeConfig</code>/validator paths to
read only own properties and use null-prototype config objects,
preventing polluted <code>auth</code>, <code>baseURL</code>,
<code>socketPath</code>, <code>beforeRedirect</code>, and
<code>insecureHTTPParser</code> from influencing requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a></strong>)</li>
<li><strong>SSRF via <code>socketPath</code>:</strong> Rejects
non-string <code>socketPath</code> values and adds an opt-in
<code>allowedSocketPaths</code> config option to restrict permitted Unix
domain socket paths, returning <code>AxiosError</code>
<code>ERR_BAD_OPTION_VALUE</code> on mismatch. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
<li><strong>Supply-chain Hardening:</strong> Added <code>.npmrc</code>
with <code>ignore-scripts=true</code>, lockfile lint CI, non-blocking
reproducible build diff, scoped CODEOWNERS, expanded
<code>SECURITY.md</code>/<code>THREATMODEL.md</code> with provenance
verification (<code>npm audit signatures</code>), 60-day resolution
policy, and maintainer incident-response runbook. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong><code>allowedSocketPaths</code> Config Option:</strong> New
request config option (and TypeScript types) to allowlist Unix domain
socket paths used by the Node http adapter; backwards compatible when
unset. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>Keep-alive Socket Memory Leak:</strong> Installs a single
per-socket <code>error</code> listener tracking the active request via
<code>kAxiosSocketListener</code>/<code>kAxiosCurrentReq</code>,
eliminating per-request listener accumulation,
<code>MaxListenersExceededWarning</code>, and linear heap growth under
concurrent or long-running keep-alive workloads (fixes <a
href="https://redirect.github.com/axios/axios/issues/10780">#10780</a>).
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a></strong>)</li>
</ul>
<h2>🔧 Maintenance &amp; Chores</h2>
<ul>
<li><strong>Changelog:</strong> Updated <code>CHANGELOG.md</code> with
v1.15.1 release notes. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a></strong>)</li>
</ul>
<p><a
href="https://github.com/axios/axios/compare/v1.15.1...v1.15.2">Full
Changelog</a></p>
<h2>v1.15.1</h2>
<p>This release ships a coordinated set of security hardening fixes
across headers, body/redirect limits, multipart handling, and
XSRF/prototype-pollution vectors, alongside a broad sweep of bug fixes,
test migrations, and threat-model documentation updates.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Header Injection Hardening:</strong> Tightened validation
and sanitisation across request header construction to close the
header-injection attack surface. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10749">#10749</a></strong>)</li>
<li><strong>CRLF Stripping in Multipart Headers:</strong> Correctly
strips CR/LF from multipart header values to prevent injection via field
names and filenames. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10758">#10758</a></strong>)</li>
<li><strong>Prototype Pollution / Auth Bypass:</strong> Replaced unsafe
<code>in</code> checks with <code>hasOwnProperty</code> to prevent
authentication bypass via prototype pollution on config objects, with
additional regression tests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10761">#10761</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10760">#10760</a></strong>)</li>
<li><strong><code>withXSRFToken</code> Truthy Bypass:</strong>
Short-circuits on any truthy non-boolean value, so an ambiguous config
no longer silently leaks the XSRF token cross-origin. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a></strong>)</li>
<li><strong><code>maxBodyLength</code> With Zero Redirects:</strong>
Enforces <code>maxBodyLength</code> even when <code>maxRedirects</code>
is set to <code>0</code>, closing a bypass path for oversized request
bodies. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10753">#10753</a></strong>)</li>
<li><strong>Streamed Response <code>maxContentLength</code>
Bypass:</strong> Applies <code>maxContentLength</code> to streamed
responses that previously bypassed the cap. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10754">#10754</a></strong>)</li>
<li><strong>Follow-up CVE Completion:</strong> Completes an earlier
incomplete CVE fix to fully close the regression window. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10755">#10755</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>AI-Based Docs Translations:</strong> Initial scaffold for
AI-assisted translations of the documentation site. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10705">#10705</a></strong>)</li>
<li><strong><code>Location</code> Request Header Type:</strong> Adds
<code>Location</code> to <code>CommonRequestHeadersList</code> for
accurate typing of redirect-aware requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7528">#7528</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>FormData Handling:</strong> Removes
<code>Content-Type</code> when no boundary is present on
<code>FormData</code> fetch requests, supports multi-select fields,
cancels <code>request.body</code> instead of the source stream on fetch
abort, and fixes a recursion bug in form-data serialisation. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7314">#7314</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10676">#10676</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10702">#10702</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10726">#10726</a></strong>)</li>
<li><strong>HTTP Adapter:</strong> Handles socket-only request errors
without leaking keep-alive listeners. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10576">#10576</a></strong>)</li>
<li><strong>Progress Events:</strong> Clamps <code>loaded</code> to
<code>total</code> for computable upload/download progress events.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/7458">#7458</a></strong>)</li>
<li><strong>Types:</strong> Aligns <code>runWhen</code> type with the
runtime behaviour in <code>InterceptorManager</code> and makes response
header keys case-insensitive. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7529">#7529</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10677">#10677</a></strong>)</li>
<li><strong><code>buildFullPath</code>:</strong> Uses strict equality in
the base/relative URL check. (<strong><a
href="https://redirect.github.com/axios/axios/issues/7252">#7252</a></strong>)</li>
<li><strong><code>AxiosURLSearchParams</code> Regex:</strong> Improves
the regex used for param serialisation to avoid edge-case mismatches.
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10736">#10736</a></strong>)</li>
<li><strong>Resilient Value Parsing:</strong> Parses out header/config
values instead of throwing on malformed input. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10687">#10687</a></strong>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/axios/axios/blob/v1.x/CHANGELOG.md">axios's
changelog</a>.</em></p>
<blockquote>
<h2>v1.15.2 - April 21, 2026</h2>
<p>This release delivers prototype-pollution hardening for the Node HTTP
adapter, adds an opt-in <code>allowedSocketPaths</code> allowlist to
mitigate SSRF via Unix domain sockets, fixes a keep-alive socket memory
leak, and ships supply-chain hardening across CI and security docs.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li><strong>Prototype Pollution Hardening (HTTP Adapter):</strong>
Hardened the Node HTTP adapter and
<code>resolveConfig</code>/<code>mergeConfig</code>/validator paths to
read only own properties and use null-prototype config objects,
preventing polluted <code>auth</code>, <code>baseURL</code>,
<code>socketPath</code>, <code>beforeRedirect</code>, and
<code>insecureHTTPParser</code> from influencing requests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a></strong>)</li>
<li><strong>SSRF via <code>socketPath</code>:</strong> Rejects
non-string <code>socketPath</code> values and adds an opt-in
<code>allowedSocketPaths</code> config option to restrict permitted Unix
domain socket paths, returning <code>AxiosError</code>
<code>ERR_BAD_OPTION_VALUE</code> on mismatch. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
<li><strong>Supply-chain Hardening:</strong> Added <code>.npmrc</code>
with <code>ignore-scripts=true</code>, lockfile lint CI, non-blocking
reproducible build diff, scoped CODEOWNERS, expanded
<code>SECURITY.md</code>/<code>THREATMODEL.md</code> with provenance
verification (<code>npm audit signatures</code>), 60-day resolution
policy, and maintainer incident-response runbook. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a></strong>)</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong><code>allowedSocketPaths</code> Config Option:</strong> New
request config option (and TypeScript types) to allowlist Unix domain
socket paths used by the Node http adapter; backwards compatible when
unset. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a></strong>)</li>
</ul>
<h2>🐛 Bug Fixes</h2>
<ul>
<li><strong>Keep-alive Socket Memory Leak:</strong> Installs a single
per-socket <code>error</code> listener tracking the active request via
<code>kAxiosSocketListener</code>/<code>kAxiosCurrentReq</code>,
eliminating per-request listener accumulation,
<code>MaxListenersExceededWarning</code>, and linear heap growth under
concurrent or long-running keep-alive workloads (fixes <a
href="https://redirect.github.com/axios/axios/issues/10780">#10780</a>).
(<strong><a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a></strong>)</li>
</ul>
<h2>🔧 Maintenance &amp; Chores</h2>
<ul>
<li><strong>Changelog:</strong> Updated <code>CHANGELOG.md</code> with
v1.15.1 release notes. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a></strong>)</li>
</ul>
<p><a
href="https://github.com/axios/axios/compare/v1.15.1...v1.15.2">Full
Changelog</a></p>
<hr />
<h2>v1.15.1 - April 19, 2026</h2>
<p>This release ships a coordinated set of security hardening fixes
across headers, body/redirect limits, multipart handling, and
XSRF/prototype-pollution vectors, alongside a broad sweep of bug fixes,
test migrations, and threat-model documentation updates.</p>
<h2>🔒 Security Fixes</h2>
<ul>
<li>
<p><strong>Header Injection Hardening:</strong> Tightened validation and
sanitisation across request header construction to close the
header-injection attack surface. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10749">#10749</a></strong>)</p>
</li>
<li>
<p><strong>CRLF Stripping in Multipart Headers:</strong> Correctly
strips CR/LF from multipart header values to prevent injection via field
names and filenames. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10758">#10758</a></strong>)</p>
</li>
<li>
<p><strong>Prototype Pollution / Auth Bypass:</strong> Replaced unsafe
<code>in</code> checks with <code>hasOwnProperty</code> to prevent
authentication bypass via prototype pollution on config objects, with
additional regression tests. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10761">#10761</a></strong>,
<strong><a
href="https://redirect.github.com/axios/axios/issues/10760">#10760</a></strong>)</p>
</li>
<li>
<p><strong><code>withXSRFToken</code> Truthy Bypass:</strong>
Short-circuits on any truthy non-boolean value, so an ambiguous config
no longer silently leaks the XSRF token cross-origin. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a></strong>)</p>
</li>
<li>
<p><strong><code>maxBodyLength</code> With Zero Redirects:</strong>
Enforces <code>maxBodyLength</code> even when <code>maxRedirects</code>
is set to <code>0</code>, closing a bypass path for oversized request
bodies. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10753">#10753</a></strong>)</p>
</li>
<li>
<p><strong>Streamed Response <code>maxContentLength</code>
Bypass:</strong> Applies <code>maxContentLength</code> to streamed
responses that previously bypassed the cap. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10754">#10754</a></strong>)</p>
</li>
<li>
<p><strong>Follow-up CVE Completion:</strong> Completes an earlier
incomplete CVE fix to fully close the regression window. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10755">#10755</a></strong>)</p>
</li>
</ul>
<h2>🚀 New Features</h2>
<ul>
<li><strong>AI-Based Docs Translations:</strong> Initial scaffold for
AI-assisted translations of the documentation site. (<strong><a
href="https://redirect.github.com/axios/axios/issues/10705">#10705</a></strong>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="582934382e"><code>5829343</code></a>
chore(release): prepare release 1.15.2 (<a
href="https://redirect.github.com/axios/axios/issues/10789">#10789</a>)</li>
<li><a
href="4709a48fa2"><code>4709a48</code></a>
fix: added fix for memory leak in sockets (<a
href="https://redirect.github.com/axios/axios/issues/10788">#10788</a>)</li>
<li><a
href="be3336014e"><code>be33360</code></a>
chore: update changelog (<a
href="https://redirect.github.com/axios/axios/issues/10781">#10781</a>)</li>
<li><a
href="4791514466"><code>4791514</code></a>
fix: more header pollutions (<a
href="https://redirect.github.com/axios/axios/issues/10779">#10779</a>)</li>
<li><a
href="6feafcff6c"><code>6feafcf</code></a>
fix: socket issue (<a
href="https://redirect.github.com/axios/axios/issues/10777">#10777</a>)</li>
<li><a
href="302e2739c6"><code>302e273</code></a>
docs: update docs, add a couple actions etc (<a
href="https://redirect.github.com/axios/axios/issues/10776">#10776</a>)</li>
<li><a
href="ac42446be5"><code>ac42446</code></a>
chore(release): prepare release 1.15.1 (<a
href="https://redirect.github.com/axios/axios/issues/10767">#10767</a>)</li>
<li><a
href="908f2206b6"><code>908f220</code></a>
docs: update threatmodel (<a
href="https://redirect.github.com/axios/axios/issues/10765">#10765</a>)</li>
<li><a
href="f93f815525"><code>f93f815</code></a>
docs: added docs around potential decompressions bomb (<a
href="https://redirect.github.com/axios/axios/issues/10763">#10763</a>)</li>
<li><a
href="1728aa1b15"><code>1728aa1</code></a>
fix: short-circuits on any truthy non-boolean in withXSRFToken (<a
href="https://redirect.github.com/axios/axios/issues/10762">#10762</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/axios/axios/compare/v1.15.0...v1.15.2">compare
view</a></li>
</ul>
</details>
<br />
2026-05-23 16:46:18 -05:00
Nabeel S.
771d8193ad
Merge branch 'main' into dependabot/npm_and_yarn/axios-1.15.2 2026-05-23 16:19:03 -05:00
Nabeel S.
f5dc0e4c5f
[8.x] refactor(FlightService, PirepService, UserService): replace UserService usage with direct user methods for subfleet and aircraft access (#2217)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Consolidated aircraft/subfleet access into model-driven authorization
and moved fare override resolution to API responses for more consistent,
permission-aware results.

* **Tests**
* Added feature and unit tests validating subfleet/aircraft access rules
and enforcing query-count bounds on hot paths.

* **Chores**
* Added database indexes to improve query performance for
aircraft/subfleet and type-rating lookups.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2217?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-23 16:16:10 -05:00
Arthur Pariente
dcc25a108f
Merge branch 'main' into refactor-subfleets-filtering 2026-05-23 22:35:50 +02:00
Nabeel Shahzad
9dc78d22cd
I guess we needed the data migrator 2026-05-23 12:34:05 -05:00
Nabeel S.
835034707c
feat(schema-modernization): foundations for RouteForge (#2215)
Schema, model, Filament, cron, and visibility-semantics foundations
required by the RouteForge change. Four phases delivered as one coherent
change. Verified against four spec files (flight-bundles,
flight-time-storage, subfleet-capability, flight-visibility).

Phase 1.1 \xe2\x80\x94 Flight time columns
- flights.departure_time, flights.arrival_time TIME NULL
- Flight model accessors (Hi-format) + mutators (FlightTimeParser)
- FlightTimeParser supports 9 formats + Z/L/tz suffix stripping
- Migration backfills inline via chunked raw DB::table->update()
- Legacy dpt_time/arr_time VARCHAR columns preserved for one release

Phase 1.2 \xe2\x80\x94 Subfleet capability columns
- subfleets.cruise_speed, max_range_nm, route_types (CSV VARCHAR(64))
- FlightTypesCast splits/sorts/dedupes/logs-on-invalid
- routeforge defaults in config/phpvms.php
- SubfleetForm Operational Capability section

Phase 1.3 \xe2\x80\x94 Flight Bundles
- flight_bundles table + flights.bundle_id NOT NULL FK
- Default bundle seeded inline; existing flights backfilled
- FlightBundle model + factory + BundleObserver (queued recompute)
- FlightBundleResource as sole Flights nav entry (slug=flights,
icon=OutlinedMap, sort=2)
- Nested FlightResource under FlightBundleResource (slug=flight,
parentResource, create+edit only, no nav entry)
- FlightsRelationManager with relatedResource for full-page row actions
- FlightForm: bundle selector removed (route-bound), date inputs hidden
when parent bundle owns dates, XSS-safe placeholder link
- Date columns stored as TIMESTAMP UTC, UI converts to local tz
- Soft-deleting a bundle leaves children non-deleted; visibility
recompute sets visible=false (no cascade delete)
- Spatie Shield permissions: flight_bundle.* seeded

Phase 1.4 \xe2\x80\x94 Visibility semantics
- flights.active renamed to flights.enabled (admin source of truth)
- flights.visible is cron-managed combined state
- SetActiveFlights cron deleted; SetVisibleFlights cron added
- Two-pass bulk SQL UPDATE scoped by bundle, chunks of 500
- RecomputeBundleVisibility queued job dispatched on bundle save/restore
- Flight::scopeVisible added; scopeActive retained as plain alias
- Pilot-facing read sites use ->visible() (AirportController fix)
- FlightResource JSON + FlightExporter CSV expose both enabled+active
- Four-state status badge (Disabled / Disabled by Bundle / Enabled Out
of Window / Enabled & In Window) replaces Visible toggle
- 5 visibility indexes added

Verification
- 469 Pest tests passed (2058 assertions)
- phpstan level 5 clean (646 files)
- pint --test clean
- rector --dry-run clean
- All four spec files PASS (28 requirements + 36 scenarios verified by
kimi-spec-reviewer)

Migration path documented in docs/UPGRADING.md and openspec/changes/
schema-modernization-for-routeforge/.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Flight Bundles: Group flights together with shared visibility and
scheduling controls via the admin panel.
* Subfleet Capabilities: Added optional fields for cruise speed, max
range, and route type restrictions.
* Improved time handling with enhanced parsing and validation for flight
times.

* **Bug Fixes**
* Flight visibility logic now computed from bundle and flight enabled
states with optional date windows.

* **Documentation**
  * Added upgrade guide covering migration steps and behavior changes.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2215?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-23 12:11:49 -05:00
Nabeel Shahzad
90ddec5edc
Update gitignore/rebase 2026-05-23 12:06:29 -05:00
Nabeel Shahzad
612c173dab
Remove is_default and composer update
# Conflicts:
#	composer.lock
2026-05-23 12:05:32 -05:00
Nabeel Shahzad
2f4f316ea7
refactor(api): explicitly project departure_time/arrival_time
Make FlightResource the single source of truth for the time keys in
the API response instead of relying on Eloquent's default cast
serialization + the absence of \$hidden. Adds explicit
\$res['departure_time'] / \$res['arrival_time'] projections (H:i:s
strings) alongside the existing \$res['dpt_time'] / \$res['arr_time']
(Hi strings) aliases.

Stale comment also fixed -- previously said the structured columns
were kept out of the response via Flight::\$hidden, which is no longer
true after that array was removed in the prior commit.

Behavior unchanged in practice (the cast already emitted the same
string), but the resource is now self-documenting and decoupled from
future changes to Flight::\$hidden.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
5cc77beb41
refactor(flight): expose departure_time/arrival_time directly
Remove `$hidden = ['departure_time', 'arrival_time']` from the Flight
model. The structured TIME columns are public surface and should be
queryable / inspectable via the model's default serialization, not
hidden behind the resource-only `dpt_time` / `arr_time` projection.

API surface (now)
- `departure_time` / `arrival_time`: emitted by Eloquent as `H:i:s`
  strings via the datetime:H:i:s cast.
- `dpt_time` / `arr_time`: still projected by FlightResource as legacy
  `Hi` strings for backward-compat consumers.

Tests
- FlightResourceTimeFieldsTest now asserts presence of all four keys.
- FlightListShapeTest's assertJsonStructure adds the four time keys
  to lock the API contract.

All gates green; affected suites: 97 tests pass.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
be1b32c67b
refactor(flight): move dpt_time/arr_time to API resource only
Reverses the modern Attribute-method approach: the Flight model no
longer carries dptTime/arrTime accessors. Instead, the canonical
columns are `departure_time` / `arrival_time` (Carbon, datetime:H:i:s
cast); the legacy `dpt_time` / `arr_time` `Hi`-formatted strings are
projected onto the JSON payload only by FlightResource at API response
time.

Why
- Public Attribute methods on a model are queryable surface and shouldn't
  be hidden behind protected visibility just to satisfy Larastan's
  modelAppends rule. Removing the methods (and the matching `$appends`
  entries) sidesteps the rule entirely.
- Keeps the API contract stable (`dpt_time`/`arr_time` still appear in
  responses, `departure_time`/`arrival_time` stay hidden) while
  internal code paths use the structured columns directly.

Model
- Removed `dptTime()` / `arrTime()` Attribute methods from Flight.
- Removed `dpt_time` / `arr_time` from `$appends`.
- Kept `departure_time` / `arrival_time` in `$hidden` (matches the
  contract locked by FlightResourceTimeFieldsTest).
- Dropped the now-unused `FlightTimeParser` import.

API resource
- FlightResource::toArray() now formats `dpt_time` / `arr_time` from
  the Carbon columns at response time. BidFlightResource inherits the
  projection via parent::toArray().

Form / factory
- FlightForm: TimePicker now binds to `departure_time` / `arrival_time`.
- FlightFactory: default state generates `departure_time` /
  `arrival_time` instead of the legacy keys.

Import / export plumbing
- Filament FlightImporter: kept the `dpt_time` / `arr_time` CSV column
  headers for backward compat; `fillRecordUsing` now parses via
  FlightTimeParser and writes through setAttribute('departure_time'/
  'arrival_time') to satisfy PHPStan's strict property typing.
- Filament FlightExporter: same CSV headers, `state()` callbacks
  format `Hi` from the structured columns.
- Service FlightImporter: transforms legacy keys to structured keys
  in import() before firstOrNew mass-assign.
- Service FlightExporter: overrides the array keys after the column
  loop so CSV columns continue to use the legacy header names with
  Carbon-derived values.
- LegacyImporter (old phpvms import): parses source `deptime`/`arrtime`
  into `departure_time`/`arrival_time` directly.

Tests
- Deleted tests/Unit/Models/FlightTimeAccessorTest.php (covered behavior
  that no longer exists on the model).
- FlightResourceTimeFieldsTest now seeds via `departure_time` /
  `arrival_time` factory state; assertions on the projected
  `dpt_time` / `arr_time` keys remain.
- ImporterTest: end-of-import assertions read
  `$flight->departure_time->format('Hi')` instead of `$flight->dpt_time`.

All four gates green; affected suites: 104 tests pass.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
766ccd4710
refactor(flight): use Attribute syntax for times
Convert legacy getDptTimeAttribute / setDptTimeAttribute /
getArrTimeAttribute / setArrTimeAttribute methods on the Flight
model to the modern Illuminate\Database\Eloquent\Casts\Attribute
return style (matches existing days() accessor at line 318).

Each pair collapses into a single protected method returning
Attribute::make(get: ..., set: ...) where the set closure returns
an array mapping the backing column (departure_time / arrival_time)
to the parsed value via FlightTimeParser::parse.

Notes
- Visibility must be protected, not public: Larastan's
  rules.modelAppends rule only detects virtual accessors on
  protected Attribute methods. With public it fails with
  'Property dpt_time does not exist in model.'
- Departure_time / arrival_time docblocks corrected from
  string|null to Carbon|null to match the datetime:H:i:s cast at
  Flight::casts(). Without this, PHPStan flags ?->format('Hi') as
  method.nonObject.

All four gates remain green.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
3f4fa947aa
Remove prompt files 2026-05-23 12:05:09 -05:00
Nabeel Shahzad
2022246fc3
refactor(schema-modernization): apply PR #2215 review feedback
Sweep of CodeRabbit + arthurpar06 review items on PR #2215
(feat/route-bundles).

Bug fixes
- FlightsRelationManager: gate OldExport/OldImport actions on
  !config('phpvms.use_queued_filament_imports') so they don't render
  alongside the queued Filament Import/Export actions.
- FlightTimeBackfiller: move \$parsed++ into each per-field success
  branch. Counters were row-vs-field unit mismatched, making the
  completion log misleading (parsed=rows but failures=fields).
- FlightTimeBackfillerTest: replace Log::shouldReceive('warning')
  ->zeroOrMoreTimes() with ->once()->with(...) and Mockery::on(...)
  payload matcher; the old expectation asserted nothing. Cast id to
  int in the matcher (SQLite returns string IDs).
- FlightListShapeTest: assert both 'enabled' and 'active' keys in
  the API contract structure (resource emits both as backward-compat
  alias; previously only 'active' was locked in).

H1 — native AsEnumCollection for route_types
- Migration ..._add_capability_columns_to_subfleets_table.php: change
  route_types from string(64) to json(). Portable across MySQL,
  PostgreSQL and SQLite. Column is new in this PR so no data
  migration needed.
- Subfleet model: cast switches from custom FlightTypesCast to
  AsEnumCollection::of(FlightType::class).
- Delete app/Casts/FlightTypesCast.php and its unit test.
- Test updates explicitly document the behavior shift: empty selection
  now stored as JSON [] rather than collapsed to null. Custom
  behaviors lost: auto-dedupe, auto-sort, log+drop on invalid token.
  None had consumers; future business logic can decide on the
  null-vs-empty semantic.

H9 — split backfill into data migration
- Schema migration ..._add_time_columns_to_flights_table.php no
  longer runs the backfill inline; just creates the columns.
- New database/migrations_data/2026_05_19_000000_backfill_flight_times.php
  calls FlightTimeBackfiller::run() and is picked up by
  MigrationService::runAllDataMigrations() on next admin update.

H2 — command rename to phpvms: prefix
- PreserveHiddenVisibility signature: flights:preserve-hidden-visibility
  → phpvms:preserve-hidden-visibility for consistency with the rest
  of the namespace (phpvms:dev-install, :importer, :email-test,
  :version).
- Updated refs in console test, docs/UPGRADING.md, and openspec
  change-tracking docs (tasks, design, proposal).

H10 — composer scripts
- Add --parallel flag to 'pint' and 'pint:test' so AGENTS.md's
  'composer pint --test' command uses multi-threading.

H5 — native badge for FlightForm status entry
- Refactor status_badge TextEntry from manual HtmlString + raw <span>
  markup to TextEntry::badge()->color()->state(). Removes the need
  for ->html(), manual e() escaping, and hardcoded Tailwind classes;
  inherits Filament theming and dark-mode automatically.

R2-N — defense in depth
- Wrap :url substitution in e() in parentBundleOwnedDatesMessage().
  FlightBundleResource::getUrl() is safe-by-construction but the
  translation places :url in HTML context.

Collateral
- pint --dirty added an explicit ': array' return type to
  FlightResource::toArray() (it had only @return array in PHPDoc).
  BidFlightResource::toArray() needed the same change for covariance;
  PHPStan would otherwise fail with method.childReturnType.

Prior session items (also included in this commit since not yet
committed):
- FlightTypesCast wrote validation + invalid-token logging.
- FlightsTable bundle-disabled badge with eager-loaded bundle.
- FlightImporter (Filament) added exists:flight_bundles,id rule.
- FlightImporter (Service) memoized default bundle id to avoid N+1.
- FlightForm: minDate as closure that skips for edit; fixed static
  cache leak by using Laravel's container instead.
- SetVisibleFlights: DB::table → Flight::query() for ORM consistency.
- FlightBundle: #[Scope] attribute on visible scope; hasDates() as
  has_dates Attribute accessor.
- FlightBundleTest, SubfleetCapabilityTest, FlightsBulkActionsTest
  updated for new behaviors.
- ShieldSeeder dead-code cleanup.

All four pre-PR gates green: pint --test, phpstan, rector --dry-run,
pest on affected paths (180 tests passed).
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
f450c360fe
Flight bulk actions 2026-05-23 12:05:09 -05:00
Nabeel Shahzad
deb7afa3f8
test: force APP_ENV=testing in \$_SERVER too (fix 9 local test failures)
Root cause: shell APP_ENV=development leaks into \$_SERVER via PHP's
variables_order=EGPCS. phpunit.xml <env force> sets \$_ENV + putenv()
but NOT \$_SERVER. Dotenv's RepositoryBuilder default adapter order
puts ServerConstAdapter before EnvConstAdapter — so \$_SERVER wins.

Consequences when env() returns 'development' during tests:
- runningUnitTests() = false (checks app['env'] === 'testing')
- PreventRequestForgery middleware doesn't bypass → POST/PUT → 419
  (6 RegistrationTest failures + 1 UserTest profile update failure)
- Filament/Livewire panel behaviour differs → fillForm validation
  fires on stale state (2 Flight resource tests)

Fix: also force \$_SERVER['APP_ENV']='testing' via <server> directive.
PHPUnit treats <server> + <env> as separate superglobals.

Local: 460/9 → 471/0 (no other changes). CI unaffected (CI shell
doesn't export APP_ENV so \$_SERVER was already empty).
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
4d3efef526
refactor(schema-modernization): migrate Placeholder → TextEntry (8.6)
Filament\Forms\Components\Placeholder is @deprecated in Filament 5
(see vendor/filament/forms/src/Components/Placeholder.php — extends
TextEntry, ->content() just calls ->state()).

Migrate both FlightForm sites:
- bundle_dates_message: HtmlString anchor for parent-bundle-owns-dates
- status_badge: HtmlString colored badge for flight status

TextEntry auto-detects Htmlable state via CanFormatState::formatState
(vendor/filament/.../Concerns/CanFormatState.php:387-389), blade
unescapes via <?= ... ?>. Added explicit ->html() to both for clarity
+ safety.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
1d5f0ac177
test(schema-modernization): cover migration backfill loop (8.7)
Extract the 2026_05_19_add_time_columns migration's inline chunk loop
into app/Support/FlightTimeBackfiller::run() so the parse+update+log
behaviour can be tested directly. Migration now calls the service.

New tests/Feature/Support/FlightTimeBackfillerTest.php covers:
- happy path: 3 parseable dpt_time formats (0800/08:00/8am) → 08:00:00
- unparseable input: 'not a time' → departure_time NULL, failures=1
- idempotency: rows with departure_time already set are skipped

Tests bypass Flight factory + insert via DB::table() because the
Flight mutator writes to departure_time, not the legacy dpt_time
column the backfill reads. Log facade asserted via shouldReceive
('warning')->zeroOrMoreTimes() — return-value (parsed/failures)
proves the warning code path executed. Tightening to exact message
match was flaky due to Faker-internal Log::channel chain noise.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
89e6b42272
refactor(schema-modernization): replace flightBundle alias w/ inverseRelationship (8.4)
Filament 5's ParentResourceRegistration auto-derives inverse rel name
from camelCase(classBasename(parentModel)) → 'flightBundle'. The
Flight model defines bundle() as the actual relation, so the original
patch added flightBundle() as a defensive alias.

Cleaner: override getParentResourceRegistration() to call
inverseRelationship('bundle'). Removes model-side dead code; keeps
bundle() as the sole BelongsTo. Behaviour identical:
nested CreateFlight + EditFlight still bind parent via
$flight->bundle() instead of $flight->flightBundle().
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
d05c339023
perf(schema-modernization): memoize FlightForm::resolveParentBundle (8.3)
Create page has 4 visibility/content closures that each invoke
resolveParentBundle() → FlightBundle::find($value). Without memoization
that's 4 identical queries per form render. Add request-scoped static
cache; collapses to 1 query.

Cache holds null results too (array_key_exists vs isset) so a missing
bundle isn't re-queried. Form is short-lived per request → no explicit
invalidation needed. Record-based path (edit page) unchanged.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
59e7cf29d3
refactor(schema-modernization): remove redundant bundle_id injection (8.1)
CreateRecord::handleRecordCreation auto-associates via parent
relationship — vendor/filament/filament/src/Resources/Pages/CreateRecord.php:213-229
calls getParentResourceRegistration()->getRelationship($parent)->save($record),
which sets bundle_id via HasMany FK. Aircraft sibling proves pattern:
CreateAircraft has no subfleet_id injection.

CreateFlight::mutateFormDataBeforeCreate retained for flight_time
HH:MM → minutes conversion; bundle_id block + unused FlightBundle
import dropped.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
0023118383
refactor(schema-modernization): phase A follow-ups (8.2, 8.5, 8.8)
8.2 FlightForm::resolveParentBundle — drop 3 speculative route param
names (bundle, flightBundle, record), keep only canonical flight_bundle.
Verified via route:list — admin/flights/{flight_bundle}/flight/...

8.5 FlightsNavigationTest — replace ReflectionClass with Filament public
API (getParentResource). Added getParentResourceRegistration assertion
to prove nav suppression (HasNavigation::registerNavigationItems skips
when parent registration present). shouldRegisterNavigation stays true
by default — wrong API for this check.

8.8 composer.json — register pint + pint:test scripts so AGENTS.md
pre-PR checklist works.
2026-05-23 12:05:09 -05:00
Nabeel Shahzad
28f49b97c3
feat(schema-modernization): foundations for RouteForge
Schema, model, Filament, cron, and visibility-semantics foundations
required by the RouteForge change. Four phases delivered as one
coherent change. Verified against four spec files (flight-bundles,
flight-time-storage, subfleet-capability, flight-visibility).

Phase 1.1 \xe2\x80\x94 Flight time columns
- flights.departure_time, flights.arrival_time TIME NULL
- Flight model accessors (Hi-format) + mutators (FlightTimeParser)
- FlightTimeParser supports 9 formats + Z/L/tz suffix stripping
- Migration backfills inline via chunked raw DB::table->update()
- Legacy dpt_time/arr_time VARCHAR columns preserved for one release

Phase 1.2 \xe2\x80\x94 Subfleet capability columns
- subfleets.cruise_speed, max_range_nm, route_types (CSV VARCHAR(64))
- FlightTypesCast splits/sorts/dedupes/logs-on-invalid
- routeforge defaults in config/phpvms.php
- SubfleetForm Operational Capability section

Phase 1.3 \xe2\x80\x94 Flight Bundles
- flight_bundles table + flights.bundle_id NOT NULL FK
- Default bundle seeded inline; existing flights backfilled
- FlightBundle model + factory + BundleObserver (queued recompute)
- FlightBundleResource as sole Flights nav entry (slug=flights,
  icon=OutlinedMap, sort=2)
- Nested FlightResource under FlightBundleResource (slug=flight,
  parentResource, create+edit only, no nav entry)
- FlightsRelationManager with relatedResource for full-page row actions
- FlightForm: bundle selector removed (route-bound), date inputs
  hidden when parent bundle owns dates, XSS-safe placeholder link
- Date columns stored as TIMESTAMP UTC, UI converts to local tz
- Soft-deleting a bundle leaves children non-deleted; visibility
  recompute sets visible=false (no cascade delete)
- Spatie Shield permissions: flight_bundle.* seeded

Phase 1.4 \xe2\x80\x94 Visibility semantics
- flights.active renamed to flights.enabled (admin source of truth)
- flights.visible is cron-managed combined state
- SetActiveFlights cron deleted; SetVisibleFlights cron added
- Two-pass bulk SQL UPDATE scoped by bundle, chunks of 500
- RecomputeBundleVisibility queued job dispatched on bundle save/restore
- Flight::scopeVisible added; scopeActive retained as plain alias
- Pilot-facing read sites use ->visible() (AirportController fix)
- FlightResource JSON + FlightExporter CSV expose both enabled+active
- Four-state status badge (Disabled / Disabled by Bundle / Enabled
  Out of Window / Enabled & In Window) replaces Visible toggle
- 5 visibility indexes added

Verification
- 469 Pest tests passed (2058 assertions)
- phpstan level 5 clean (646 files)
- pint --test clean
- rector --dry-run clean
- All four spec files PASS (28 requirements + 36 scenarios verified
  by kimi-spec-reviewer)

Migration path documented in docs/UPGRADING.md and openspec/changes/
schema-modernization-for-routeforge/.
2026-05-23 12:05:09 -05:00
Nabeel S.
28f70a7d0e
[8.x] refactor: remove VaCentral package and update airport lookup implementation (#2216)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Removed Features**
* Telemetry and usage analytics reporting functionality has been removed
from the application.

* **Changes**
* Airport data lookup now retrieves information directly from the PHPVMS
API instead of through an intermediary service, improving system
integration.
* External dependency has been removed, reducing application
dependencies and footprint.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/phpvms/phpvms/pull/2216?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-23 12:05:01 -05:00
Arthur Pariente
2ba8205c10
refactor(BidService, Flight): optimize eager-loading of subfleets and aircraft for improved performance 2026-05-23 11:18:22 +02:00
Arthur Pariente
8caf34420b
refactor(FlightService, PirepService, UserService): replace UserService usage with direct user methods for subfleet and aircraft access 2026-05-23 10:58:36 +02:00
Arthur Pariente
d823e72238
refactor: remove VaCentral package and update airport lookup implementation 2026-05-22 18:27:54 +02:00
dependabot[bot]
4a91cee03f
build(deps): bump axios from 1.15.0 to 1.15.2
Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.0...v1.15.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 16:07:48 +00:00
Nabeel S.
de34422008
Updating styling and looks of the installer/admin/system filament pages (#2211)
* fix(installer): handle missing DB and fix CSRF middleware class

- InstalledCheck: wrap Schema::hasTable in try-catch to handle when
database is unreachable or does not exist, preventing crash before
installer can redirect
- SystemPanelProvider, AdminPanelProvider: fix non-existent
PreventRequestForgery class to ValidateCsrfToken (Laravel 12)
- .env.example: change default SESSION_DRIVER from database to file
so fresh installs don't require sessions table before migrations run

* Filament installer/system styles and changes

* Don't include frontend js in system/admin

* Don't include frontend js in system/admin

* fix(translations): normalize 'informations' and snake_case keys

- Fix ungrammatical plural 'informations' → 'Information' across all locales
- Rename joined-word translation keys to snake_case convention:
  auth: create_account, forgot_password, full_name, email_address, etc.
  pireps: new_flight_report, flight_time, flight_level, fields_readonly, etc.
  common: newest_pilots, live_map, rights_reserved, toggle_colors
  flights: flight_time, profile: transfer_hours, widgets: live_map
  dashboard: total_hours, your_balance, your_last_report, no_reports_yet
- Update all PHP/Blade references to renamed keys
- Add missing translation keys to non-English locales (send_reset_link,
  comment, error_changing_state, fields, field_value, landing_rate, etc.)
- Fix fr/filament.php: two keys on single line
- Fix en/aircraft.php: double space in 'Dry Weight'
- Run Pint on all modified files

* Design and other small changes

* feat(admin): add News Filament resource and seed PIREP sample data

Replace the dashboard News widget with a full Filament resource that
opens Create/Edit in modals (no separate pages routed). Adds the
matching policy so filament-shield can wire permissions.

Add a dev seed script (scripts/seed_sample_pireps.php) that inserts
sample airports, subfleets, aircraft, and 10 admin-owned PIREPs (last
two PENDING) to populate an empty database for UI testing.

Fix lazy-load exception on the PIREPs admin table by eager-loading
airline, aircraft and user via modifyQueryUsing; the table only worked
before because the database had no PIREPs.

* chore(admin): pending Filament panel refactor in working tree

Pre-existing uncommitted work from earlier sessions. Not authored or
reviewed in this commit's session; batched here only to clear the
working tree.

Includes:
- Airports resource simplified to modal-via-no-route pattern
  (CreateAirport, EditAirport page stubs deleted; getPages trimmed
  to index only)
- Modules page renamed to Addons
- New plugins: ClearCachesPlugin, SidebarCollapseTogglePlugin
- ClearCaches Livewire component
- Filament admin theme.css restyle
- NavigationGroup enum + lang/common, lang/filament tweaks
- system/brand.blade.php moved to shared/brand.blade.php
- Override attribute normalization across Filament resources

* fix(review): address CodeRabbit PR #2211 review feedback

Critical
- Restore CSRF middleware (PreventRequestForgery) in Admin + System panels
- Fix Addons page redirectRoute (modules -> addons) in 3 actions
- SeederService: fix SplFileInfo TypeError, guard missing seed dir, arrow fn
- YamlSeeder: propagate $ignore_errors, fix SplFileInfo bug, guard file read,
  proper File facade import
- Installer: gate install via withoutGlobalScopes()->exists() not count(),
  drop duplicated stream() of full buffer after chunked streaming
- .env.example: APP_DEBUG/DEBUGBAR_ENABLED default to false

Bugs
- seed_sample_pireps.php: swap KSAN/KLAX lat/lon
- theme.css: drop phantom mobile ::before rule (no matching selector)
- module-links-topbar.blade: localize hardcoded Admin label
- seven/app.blade: drop @vite call referencing removed bundles

i18n
- de/auth: fix Adresse typo + translate send_reset_link
- de/installer.title: brand wordmark phpvms
- fr/auth.toc_accept: fix acceptez typo
- es-es/it/tr/jp/pt-br: translate 10 PIREP keys + send_reset_link/fill_captcha
- de/es-es/it/jp/tr/common: add toggle_colors

Cleanup
- Remove dead commented blocks (Installer headerActions, InstallSeeder,
  YamlSeeder, seed_sample_pireps section dividers)
- NewsResource label uses common.news namespace
- login-hero alt uses lowercase brand

Rector + Pint auto-fixes
- Add declare(strict_types=1) where missing
- Add arrow-fn return types for brandLogo + News table schema
- Standardize catch-block exception names

* wip: pre-plan baseline (pirep map work + admin/frontend JS split)

* feat(pireps): add PerformanceChartService for ACARS chart datasets

* feat(pireps): load fares/field_values + build performance chart payload

* refactor(pireps): drop relation manager tabs (will embed inline)

* feat(pireps): register Chart.js wrapper for performance charts

* feat(pireps): add detail page header + stat strip partial

* feat(pireps): add unified Route & Performance card partial

* feat(pireps): add notes + filed route partial

* feat(pireps): add detail sidebar partial

* feat(pireps): wire new detail blade composition + embedded RMs

* feat(pireps): style v2 detail page (Stripe-flavored, theme tokens, dark mode)

* chore(pireps): drop legacy .fi-pirep-modal-* styles

* fix(pireps): inline Acars query to satisfy PHPStan level 5

larastan does not forward #[Scope] attribute methods through
HasMany relation builders, so calling `->ofType()->orderedByCreatedAt()`
on the relation tripped method.notFound at PHPStan level 5.

Replace with explicit where() + orderBy() — same runtime behavior,
no scope-forwarding surface for the analyzer to miss.

* fix(pireps): prune wasted eager loads + preserve zero-fuel samples

- Eager-load `user.rank` (sidebar reads $pilot->rank->name and would N+1
  on every detail page view).
- Drop `comments.user`, `transactions`, `fares.fare` from loadMissing —
  the embedded relation managers issue their own queries, so preloading
  at the page level only adds round-trips for data the page itself never
  uses.
- Fix fuel series to treat a fuel value of `0` as a real sample rather
  than null. Previously `$s->fuel ? ... : null` collapsed legitimate
  empty-tanks readings into gaps.

* fix(pireps): restore eager loads for embedded relation managers

Filament relation managers ARE separate Livewire queries, but their
blade columns still reach into nested relations on each row
(e.g. PirepFare->fare in FaresRelationManager). With lazy loading
disabled globally via Model::preventLazyLoading, those accesses
hard-fail with LazyLoadingViolationException.

Restore comments.user / transactions / fares.fare to the parent
loadMissing(). Keep user.rank from the earlier fix.

Reverts the wasted-load optimization in d6a8a3b5 — the optimization
was based on speculation, not measurement, and broke real
runtime behavior. Net effect: one extra preload per detail page,
versus a 500 error.

Refs: detail.blade.php:46 @livewire(FaresRelationManager)

* fix(pireps): address review findings (eager loads, ts null guard, downsample tail)

Tier A fixes from post-merge review of the redesign branch.

ViewPirep::mount
- Add fares.pirep + field_values.pirep to loadMissing. The FaresRelationManager
  and FieldValuesRelationManager column `disabled` closures call
  `$record->pirep->read_only` per row. Under Model::preventLazyLoading
  (active in dev/test) that triggered a lazy-load violation when editing
  any fare/field value. Production silently lazy-loaded on every edit.
- Drop airline from loadMissing. No partial or relation manager touches the
  airline relation; the preload was dead weight.

PerformanceChartService::ts
- Guard against null Acars$created_at (documented Carbon|null; partial imports
  leave it unset). Use \DateTimeInterface instanceof check rather than ?->,
  because larastan narrows the model property to non-null Carbon and reports
  nullsafe.neverNull at level 5 even when the runtime type is genuinely null.

PerformanceChartService::downsample
- Always include the final sample. Previously `$i % $step === 0` could drop
  the last point when total count was not a multiple of step, hiding the
  touchdown / arrival sample from the chart. Index 0 stays included via
  the existing modulo, so first + last are both preserved.

* refactor(pireps): drop route text card, restore route bar under map

Removes the raw waypoint route card from the PIREP view (notes partial),
restores the route bar (departure/arrival ICAO + name + block times + line)
that sits below the map in route-performance partial, and re-adds its
matching CSS in the admin theme.

- resources/views/filament/pireps/detail/notes.blade.php: remove route text card
- resources/views/filament/pireps/detail/route-performance.blade.php: restore route bar block + php helpers (blockOff/blockOn/duration/unitDistance)
- resources/css/filament/admin/theme.css: restore .fi-pirep-detail-v2-route-bar styles

* wip(pireps): landing analysis card, perf chart split, comment policy + tooling

Bundles in-progress PIREP detail work plus build tooling that landed in the
working tree:

PIREP detail
- Landing analysis card: runway plan-views, scorecard polar, attitude
  indicators (route-performance partial owns the markup; see prior commit).
- PerformanceChartService: build landing payload + extra chart series.
- Sidebar + modal/detail partials: stacked custom-fields card, layout tweaks.
- PirepCommentPolicy + RelationManager wiring for per-row authorization.

Assets
- Split AlpineComponents into resources/js/components/{pirep-landing-analysis,pirep-performance-chart}.js.
- bin/build.js esbuild driver; package.json/vite.config.js/.gitignore updates.
- Drop legacy resources/js/admin/pirep-performance-chart.js (moved to components/).

Misc
- AdminPanelProvider, base_map.js, config/services.php small adjustments.
- justfile task runner.
- composer/package lock updates.

* feat(admin): improve PIREP detail page layout and theming

- Split fares/transactions into separate cards (was single Finance card)
- Fares: plain text columns, no pagination, currency formatting
- Transactions: JetBrains Mono money amounts, no pagination
- Net total row in its own card
- Sidebar facts (Pilot/Flight) use stacked layout matching PIREP fields
- JetBrains Mono font for all money values (fi-pirep-money-mono)
- Map switches CartoDB.Voyager/DarkMatter on theme change
- Admin sidebar width 14.5rem
- PIREP detail sidebar sticky positioning accounts for topbar

* View pirep page updates

* fix: admin pirep page UX improvements and chart stability

- View PIREP button: transparent background, no fill
- Flight number links to view PIREP page
- PIREP detail heading shows flight number + route (VMS7620 C.PF MMMX→MMAA)
- Hero section: pilot name + aircraft on top line, filed time + source on secondary
- Sidebar: remove sticky scroll, let entire page scroll naturally
- Performance chart: fix canvas detachment on tab switch by wrapping in wire:ignore parent and using .canvas

* Shape up finances table

* feat: add flight log timeline tab with phase-colored badges

Two-column flight strip on Flight Log tab rendering ACARS LOG entries
(type=2) with timestamp/altitude/speed badges and phase assignment
from PerformanceChartService phase detection. Sort toggle flips
between earliest-first and latest-first order.

* fix(review): address code review blockers

- phpunit.xml: restore DB_CONNECTION=sqlite (suite was hitting MySQL
  with :memory: database name, all tests 500ing on first query)
- ViewPirep::getHeading: use typed $this->record over getRecord()
  to fix 4 phpstan property.notFound errors at level 5
- NewsResource: drop user_id overwrite on EditAction; preserved the
  original author. user_id only set on create now.
- PerformanceChartService: remove discarded toFloat call on landing
  speed (no consumer anywhere in codebase)

Pre-PR checks: pint v, phpstan v, rector v, pest v (368 passed).

* chore(review): tidy review minors

- finance widgets: refresh /livewire/update comment to v4 hashed path
  format; widget visibility logic is path-agnostic so behavior unchanged
- seed_sample_pireps: make bulk PIREP loop idempotent. lookup by
  source_name + flight_number + airport pair so re-running the script
  no longer duplicates the 10 routes on each invocation

* fix(pireps): flight-log rows respect dark mode

Inline style="background: #..." on each row + badge was overriding
the dark-mode CSS rules, leaving alternating rows white in dark mode.

- blade: drop the phaseColor/phaseBg/phaseTextColor/rowBg PHP closures.
  collapse to a single $phaseBucket helper that maps a phase code to a
  bucket name (ground/climb/cruise/descent/land/neutral). row + badge
  pick up CSS modifier classes instead of inline styles.
- theme.css: add zebra + phase tint rules for the row (.--zebra,
  .--phase-climb, .--phase-land) plus six badge variants. each has a
  light + dark pair using translucent tints with brighter text so the
  badge stays readable against the gray-900 row background.

* fix(ci): guard Vite::asset() against missing build manifest

composer install runs `package:discover` which boots service providers.
AdminPanelProvider::register() called Vite::asset() eagerly, throwing
ViteManifestNotFoundException on CI / fresh clones where composer runs
before npm has built the frontend.

Move the admin maps Js asset behind an `is_file(public_path(...))`
guard so the provider registers the rest of the assets unconditionally
and skips the Vite-resolved entry until the manifest exists. The map
blade has its own @vite() at render time so end-user behavior is
unchanged once `npm run build` has run.

Verified locally by removing public/build/manifest.json and running
composer dump-autoload — package:discover now completes cleanly.

* fix(ci): guard AlpineComponent assets against missing dist files

filament:upgrade (post-autoload-dump) was throwing on CI when copying
resources/js/dist/components/pirep-performance-chart.js to the public
assets folder. The esbuild'd Alpine components are produced by
bin/build.js and not committed, so a fresh clone has no dist directory
when composer post-autoload hooks run.

Loop the components through an `is_file()` guard like the Vite manifest
check added in the previous commit. Provider registration completes
when the frontend hasn't been built; the components register the
moment `npm run build` (or `node bin/build.js`) creates the files.

Verified by removing resources/js/dist and re-running
`php artisan filament:upgrade` — clean.

* chore(ci): build frontend before composer + commit dist artifacts

Two coordinated fixes for CI ordering + asset shipping:

(A) Reorder CI so Node + frontend build run BEFORE composer install.
The Laravel post-autoload-dump hook chain (package:discover ->
filament:upgrade) boots service providers which read the Vite manifest
and copy AlpineComponent files at register time. Running composer first
threw ViteManifestNotFoundException and copy() ENOENT on a fresh clone.

Affects all three jobs in build.yml:
- `build` (matrix x3 PHP versions): Node setup moved above composer
- `artifacts` (release packager): same swap
- `docker` (image builder): Node setup added (was composer-only)

(C) Commit `resources/js/dist/components/*.js` esbuild output. Removes
the runtime dependency on `node bin/build.js` for downstream consumers
(shared hosting installs, fresh composer-create-project flows). Matches
the existing convention for `public/js/filament/*` and
`public/assets/frontend/js/*` which are already committed pre-built.

A "Verify committed dist files match a fresh build" CI step diffs the
post-build tree against HEAD so contributors who edit the AlpineComponent
sources without re-running `npm run build:components` fail the build with
a clear error rather than shipping stale dist files.

The defensive `is_file()` guards in AdminPanelProvider stay (committed
in 7fdf2b2b + 97618bb8) — belt and suspenders against partial checkouts.

Refs PR #2211 review.

* chore(js): reorganize Filament components under admin/ + lint cleanup

CI lint failures surfaced two issues that local lint had buried under
1318 warnings from the committed esbuild output (Chart.js internals):

- resources/js/app.js was an empty stub (`//`), never imported anywhere.
  Removed.
- RW_RUNWAY_TOP in pirep-landing-analysis.js was declared but never
  used. Removed.

Folded in a directory reorg while touching the same files:

- Sources move to resources/js/admin/components/. Mirrors the existing
  resources/js/admin/ tree (request.js, storage.js, maps/, etc.) which
  was created in this branch for the admin-only JS split.
- Esbuild output mirrors the source layout — moves to
  resources/js/dist/admin/components/.
- bin/build.js entry + outfile paths updated.
- AdminPanelProvider resource_path() updated to match.

Tooling guards added so dist noise stops drowning real warnings:

- .oxlintrc.json: ignore resources/js/dist/** (bundled output)
- .oxfmtrc.json: ignore resources/js/dist/** (minified output)
- bin/build.js itself was new in this branch and not yet oxfmt-clean;
  reformatted (quotes/semis/indent).

Verified locally: oxlint 0 warnings 0 errors, fmt:check clean for all
tracked files, pint v, phpstan v, rector v.

* fix(ci): three-step bootstrap to break composer<->vite chicken-and-egg

The previous "npm before composer" reorder broke the build:
resources/css/filament/admin/theme.css imports
vendor/filament/filament/resources/css/theme.css, which doesn't exist
until composer install has run. Vite resolve failed at CI line 1.

Reverting to "composer before npm" re-triggers the original failure
(ViteManifestNotFoundException during package:discover; ENOENT during
filament:upgrade) because composer post-autoload-dump boots service
providers that need the Vite manifest + dist files.

Fix: three-step bootstrap, applied to all three jobs in build.yml.

  1. composer install --no-scripts
       Pulls vendor/ (Vite needs it for theme.css @imports) WITHOUT
       firing post-autoload-dump (which would boot the providers
       prematurely).
  2. npm install + npm run build
       Now resolves the vendor CSS @import. Produces
       public/build/manifest.json and rebuilds
       resources/js/dist/admin/components/*.js.
  3. composer dump-autoload
       Re-fires post-autoload-dump. package:discover and
       filament:upgrade boot providers cleanly — the Vite manifest
       and dist files are both present.

Verified locally inside the dev container: removed vendor/,
public/build/, resources/js/dist/, ran the three steps, all clean.

The freshness check (git diff --exit-code resources/js/dist) and the
build matrix's lint/fmt/pint/pest/phpstan stages run unchanged — only
the bootstrap order in front of them changes.

* fix(installer): address CodeRabbit review feedback

- Add Response return type to InstalledCheck::handle()
- Translate hero_title for es-es, fr, pt-br locales
- Standardize brand alt text to 'phpvms'
- Null-safe source label in PIREP row blade to avoid error
  when source_name is set but source enum is null
- Render landing_rate stat box for 0 values (use !== null check)

* chore(build): stop committing compiled JS/CSS assets

Untrack frontend build artifacts and rely on CI to (re)build them:

- resources/js/dist/ AlpineComponents (output of bin/build.js)
- public/js/filament/, public/css/filament/, public/fonts/filament/
- public/css/filament-spatie-backup/

The Filament assets are re-published by 'php artisan filament:upgrade'
during composer post-autoload-dump. The AlpineComponents are produced by
'npm run build'. Both already run in build.yml and release.yml.

Also drop the now-obsolete 'verify committed dist files match a fresh
build' step in CI \u2014 dist files are no longer tracked.

* refactor(filament): remove is_file guards around admin assets

The guards were added when composer post-autoload-dump could fire before
`npm run build` produced the frontend artifacts. CI now does a three-step
bootstrap (composer install --no-scripts -> npm build -> composer
dump-autoload) so dist files and the Vite manifest always exist by the
time AdminPanelProvider::register() runs. Release tarballs ship them
pre-built.

If assets are missing at runtime we want to know immediately, not
silently degrade the admin panel.

* refactor(filament): mirror Pireps namespace under views/filament/pireps

Co-locate page views and partials under resources/views/filament/pireps/
to mirror App\Filament\Resources\Pireps namespace.

- pages/{list-pireps,view-pirep}.blade.php (was filament/resources/pireps/pages/)
- partials/row.blade.php + partials/detail/* (was filament/pireps/{row,detail}/)
- Update $view in ListPireps and ViewPirep
- Rewrite 6 @include paths

* chore(build): add npm clean script to remove built assets

Removes public/build (Vite output) and resources/js/dist (esbuild
components output) in one command.

* Add the fillament public assets back in

* Move the vite js into the page render hook, lazily load map on first use
2026-05-20 11:05:32 -05:00
Arthur Parienté
8dfba75b0f
[8.x] refactor: update console commands for improved structure and functionality (#2209)
* refactor: update console commands for improved structure and functionality

* Update Makefile

* coderabbit

* fix seeder
2026-05-13 15:05:04 -05:00
Arthur Parienté
80f90a1c46
[8.x] fix(helpers): add back deprecated public_mix and public_url functions (#2208)
fix(helpers): add back deprecated public_mix and public_url functions

Co-authored-by: Nabeel S. <99736+nabeelio@users.noreply.github.com>
2026-05-12 16:35:02 -05:00
Arthur Parienté
7490e72523
[8.x] refactor: use native php enum instead of class (#2210)
* wip

* refactor: update enum handling and improve type hints across models

* refactor: deprecate labels and select methods in HasSelect trait for better separation of concerns

* copilot suggestions
2026-05-12 16:29:41 -05:00
Arthur Parienté
7de823a0a7
[8.x] refactor: migrate to the new modern laravel structure (#2207)
* refactor: migrate to the new modern laravel structure

* fix .env.test
2026-05-07 14:58:11 -05:00