fix: 🐛 Fix middleware definition navigation. - #3
Merged
Merged
Conversation
6 of 11 tasks
10 of 11 tasks
mikebronner
added a commit
that referenced
this pull request
Jun 14, 2026
Resolve Holmes's AC #3 blocker on #60: completion offered Flux component names but never their props. Inside `<flux:button │>`, offer the resolved component's @props as attributes — reusing the goto/hover resolution path (resolve_component_path → on-disk blade → new extract_prop_names). - blade_props: add extract_prop_names, a string/bracket-aware parser that pulls prop names from @props([...]) (shorthand + keyed, nested-array safe) - main: add get_flux_attribute_context (detects the attribute position, skips attribute values and closed tags) plus a Flux prop-completion branch - main: guard resolve_component_class_file against ':' in the tag name so it no longer probes app/View/Components/Flux:button.php (Holmes follow-up) - tests: prop-name parsing, attribute/name context detection, an end-to-end 'known prop offered for a resolved Flux component', and a node_modules exclusion test pinning the corrected stub from AC #4 Slot-name completion (<flux:slot name>) is a distinct syntactic context, tracked as a follow-up. Refs #60.
10 tasks
mikebronner
added a commit
that referenced
this pull request
Jun 14, 2026
) * chore: start work on #60 * feat: ✨ Add Flux component goto-definition, hover, and completion. Bring Flux `<flux:...>` tags to parity with Blade/Livewire component handling. Flux's single-colon syntax is sugar for the `flux` anonymous-component namespace, so tags are parsed as components and the prefix is normalized to the `flux::` form, reusing the existing Blade resolution pipeline for goto-definition and hover. - Capture `<flux:component>` tags (incl. dotted names like `<flux:icon.arrow-right>`) in the tree-sitter Blade query and parser. - Resolve the `flux` namespace to its conventional sources: the app-published `resources/views/flux/`, the package source `vendor/livewire/flux`, and Flux Pro `vendor/livewire/flux-pro`. - Offer `<flux:` component-name completion from those sources. - Skip the hard "not found" diagnostic for `<flux:...>` tags, which resolve through vendor/published paths that may be absent in a session. - Remove the dead `node_modules` Flux stub and correct the misleading rescan TODO (Flux ships via Composer, not node_modules). Fixes: #60 * feat: ✨ Add Flux component prop completion at attribute position Resolve Holmes's AC #3 blocker on #60: completion offered Flux component names but never their props. Inside `<flux:button │>`, offer the resolved component's @props as attributes — reusing the goto/hover resolution path (resolve_component_path → on-disk blade → new extract_prop_names). - blade_props: add extract_prop_names, a string/bracket-aware parser that pulls prop names from @props([...]) (shorthand + keyed, nested-array safe) - main: add get_flux_attribute_context (detects the attribute position, skips attribute values and closed tags) plus a Flux prop-completion branch - main: guard resolve_component_class_file against ':' in the tag name so it no longer probes app/View/Components/Flux:button.php (Holmes follow-up) - tests: prop-name parsing, attribute/name context detection, an end-to-end 'known prop offered for a resolved Flux component', and a node_modules exclusion test pinning the corrected stub from AC #4 Slot-name completion (<flux:slot name>) is a distinct syntactic context, tracked as a follow-up. Refs #60. * fix: 🐛 Prevent Flux attribute-completion panic on multi-byte whitespace `get_flux_attribute_context` derived the partial-attribute start with `before_cursor.rfind(char::is_whitespace).map(|i| i + 1)`. `rfind` returns the byte offset of the matched char's first byte, so `+ 1` lands inside the encoding of any multi-byte whitespace (e.g. U+00A0 NO-BREAK SPACE), and the following slice panics on a non-char-boundary — crashing a live completion keystroke. Walk the char boundaries with `char_indices().rev()` and advance by `c.len_utf8()`, mirroring the existing helpers in this file. Adds a regression test for an NBSP between the tag name and the attribute. Refs #60 * fix: 🐛 Skip invalid class-path candidate for namespaced component tags `component_candidate_paths` pushed a conventional `app/View/Components/<name>.php` candidate for every tag, including namespaced ones. For `flux:button` / `pkg::badge` that yields `.../Flux:button.php` — an invalid path on Windows and a guaranteed-miss `stat` on POSIX. Namespaced forms already resolve through the PSR-4 `componentNamespace` block, so gate the conventional candidate on non-namespaced names, matching the existing `name.contains(':')` guard in `resolve_component_class_file`. Adds a regression test asserting no candidate carries a `:` in its filename and none probes the conventional class dir for a Flux tag. Refs #60
7 of 8 tasks
4 tasks
This was referenced Jun 14, 2026
Closed
Merged
Merged
8 tasks done
This was referenced Jun 15, 2026
Merged
7 tasks done
This was referenced Jun 15, 2026
7 tasks
This was referenced Jun 15, 2026
7 tasks
7 tasks done
8 tasks
This was referenced Jun 17, 2026
mikebronner
added a commit
that referenced
this pull request
Jun 18, 2026
…root targets Add a containment backstop at the file-create seam (FileAction::build_code_action, issue #199 AC #5/#6): refuse to offer any create quick-fix (View, BladeComponent, Livewire, Inertia, …) whose target_path escapes the project root, returning None instead of constructing an out-of-root ResourceOp::Create. Completes the "fail-closed containment on every FS-touching path" sweep (#130 → #143 → #148 → #194) across the third surface — the write seam, alongside the read/resolve paths. The guard uses path_within_root_lexical, NOT the fail-closed path_within_root the sibling read paths use: a create target never exists yet, so path.canonicalize() always fails for it and the fail-closed guard would refuse *every* create, including legitimate in-root ones. The lexical guard refuses out-of-root and interior-`..` escapes while admitting a not-yet-created in-root target, and still canonicalizes to catch symlink escapes when the target exists. (AC #5 named path_within_root; flagged on the PR — same class of AC defect as the #3/#4 dispute resolved via Option 1.) Add code_action_create_containment.rs: out-of-root, interior-`..`, in-root positive control, and under-root symlink-escape cases. Reframe the resolve-seam negative tests per the #199 escalation (Option 1): they assert the *invariant* (an out-of-root / symlink-escaping component never resolves), with the resolve_component_file guard documented as the innermost backstop whose reject branch is unreachable via the public API today — the upstream path_within_root_lexical filter catches both negatives first. Drop the false "only path_within_root catches" claim and correct the misleading assert messages. Fixes: #199 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mikebronner
added a commit
that referenced
this pull request
Jun 18, 2026
…for containment-invariant uniformity (#203) * chore: start work on #199 * fix: 🔒️ Add fail-closed path_within_root guard to resolve_component_file. Mirror the sibling resolve_component_existing_file: after the file_exists_cached check, refuse any candidate whose real path escapes config.root before returning it. resolve_component_path already drops out-of-root candidates with the lexical path_within_root_lexical filter, so this is defense-in-depth — it makes the fail-closed containment invariant hold uniformly across every FS-touching component resolver (the #130 → #143 → #148 → #194 chain). Add component_file_navigation_containment.rs pinning the invariant at the resolve_component_file boundary: out-of-root and under-root-symlink escapes return None, in-root files still resolve. Fixes: #199 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: 🔒️ Guard the build_code_action write/create seam against out-of-root targets Add a containment backstop at the file-create seam (FileAction::build_code_action, issue #199 AC #5/#6): refuse to offer any create quick-fix (View, BladeComponent, Livewire, Inertia, …) whose target_path escapes the project root, returning None instead of constructing an out-of-root ResourceOp::Create. Completes the "fail-closed containment on every FS-touching path" sweep (#130 → #143 → #148 → #194) across the third surface — the write seam, alongside the read/resolve paths. The guard uses path_within_root_lexical, NOT the fail-closed path_within_root the sibling read paths use: a create target never exists yet, so path.canonicalize() always fails for it and the fail-closed guard would refuse *every* create, including legitimate in-root ones. The lexical guard refuses out-of-root and interior-`..` escapes while admitting a not-yet-created in-root target, and still canonicalizes to catch symlink escapes when the target exists. (AC #5 named path_within_root; flagged on the PR — same class of AC defect as the #3/#4 dispute resolved via Option 1.) Add code_action_create_containment.rs: out-of-root, interior-`..`, in-root positive control, and under-root symlink-escape cases. Reframe the resolve-seam negative tests per the #199 escalation (Option 1): they assert the *invariant* (an out-of-root / symlink-escaping component never resolves), with the resolve_component_file guard documented as the innermost backstop whose reject branch is unreachable via the public API today — the upstream path_within_root_lexical filter catches both negatives first. Drop the false "only path_within_root catches" claim and correct the misleading assert messages. Fixes: #199 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: 🔒️ Guard sibling create paths in build_code_action multi-file actions The write/create-seam backstop (issue #199) only validated `self.target_path`, but the multi-file action types each emit a SECOND `ResourceOp::Create`: - `Livewire` → a Blade view at `get_livewire_view_path` - `BladeComponentWithClass` → a PHP class at `get_component_class_path` Both paths are derived from `self.name` — an independent diagnostic field, not coupled to `target_path`. Because `PathBuf::join`/`push` of an absolute-looking segment *replaces* the base, a forged diagnostic with an in-root `target_path` and `name = "/etc/passwd"` slipped past the guard and materialised a file outside the project root via the second create — the exact containment escape this chain exists to close. Guard each sibling path with `path_within_root_lexical(&path, root)` and return `None` on escape, and correct the guard comment that wrongly asserted every create materialises only `target_path`. Adds non-vacuous multi-file coverage in `code_action_create_containment.rs`: a `Livewire` / `BladeComponentWithClass` action with an in-root `target_path` but an escaping `name`-derived view/class path returns `None` (each would be `Some` without the guard), plus in-root positive controls for both types. Refs #199 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mikebronner
added a commit
that referenced
this pull request
Jun 18, 2026
…gnostic hint The "Expected at:" create-this-file hint baked into a "view not found" diagnostic is parsed back into a CreateFile target by `from_diagnostic`, so it is an *emitted* path. The branch sourced it via `path_within_root_lexical`, whose docstring explicitly disclaims emitted paths — its `unwrap_or(true)` admits a dangling under-root symlink whose target is missing, which a client `CreateFile` could follow out of the project tree (issues #134/#155). Add a third containment entry point, `path_within_root_emit_safe`: it shares the lexical out-of-root gate and still admits a genuinely-absent in-root path (the hint is for a missing view, so fail-closing would break "create view"), but refuses a leaf dangling symlink via `symlink_metadata` — the residual the lexical guard leaves open. Point `in_root_expected_path_hint` at it (both "Expected at:" call sites share the helper, AC #3/#4). Extract the shared lexical gate into `lexically_in_root` to keep the module's single-source-of- truth invariant. Closes Holmes's escalated AC-vs-documented-contract dispute on PR #202, per the decision to close the residual in this PR rather than defer it. Refs #201 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mikebronner
added a commit
that referenced
this pull request
Jun 18, 2026
…stic surfaces (Livewire diagnostic fallback + "Expected at:" message hint) (#202) * chore: start work on #201 * fix: 🔒️ Extend path containment guard to remaining diagnostic surfaces. Route the Livewire "component not found" diagnostic fallback through any_in_root_candidate_exists so an out-of-root loadViewsFrom/ component_namespaces registration can no longer make diagnostics stat-probe files outside the project tree, nor let an out-of-root view that exists on disk silently satisfy the check. Source the "Expected at:" message hint from the first in-root candidate (new in_root_expected_path_hint helper, shared by the view() and @extends/@include loops) so a maliciously-registered out-of-root namespace path is never echoed back to the client in the message text. Add Livewire-fallback and message-hint cases to view_diagnostic_containment. Fixes: #201 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: 🔒️ Refuse dangling under-root symlinks in the "Expected at:" diagnostic hint The "Expected at:" create-this-file hint baked into a "view not found" diagnostic is parsed back into a CreateFile target by `from_diagnostic`, so it is an *emitted* path. The branch sourced it via `path_within_root_lexical`, whose docstring explicitly disclaims emitted paths — its `unwrap_or(true)` admits a dangling under-root symlink whose target is missing, which a client `CreateFile` could follow out of the project tree (issues #134/#155). Add a third containment entry point, `path_within_root_emit_safe`: it shares the lexical out-of-root gate and still admits a genuinely-absent in-root path (the hint is for a missing view, so fail-closing would break "create view"), but refuses a leaf dangling symlink via `symlink_metadata` — the residual the lexical guard leaves open. Point `in_root_expected_path_hint` at it (both "Expected at:" call sites share the helper, AC #3/#4). Extract the shared lexical gate into `lexically_in_root` to keep the module's single-source-of- truth invariant. Closes Holmes's escalated AC-vs-documented-contract dispute on PR #202, per the decision to close the residual in this PR rather than defer it. Refs #201 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: 🔒️ Refuse non-NotFound lstat errors in emit-safe containment guard `path_within_root_emit_safe`'s `None` arm tested `symlink_metadata().is_err()`, which is true for EVERY lstat error — not just `NotFound`. A lexically-in-root candidate that fails to canonicalize and whose lstat returns `EACCES` (a no-search-permission parent hiding a dangling under-root symlink), `ENOTDIR`, or `ELOOP` was therefore ADMITTED and echoed into the "Expected at:" diagnostic hint, where a client `CreateFile` could follow it out of the project tree — the exact escape this guard exists to refuse. The doc comment already specified the discriminating condition as `NotFound`; the code failed open against it. Discriminate on `NotFound` so the guard fails closed on anything it cannot prove absent, admitting only a genuinely-absent speculative create target. - `is_err()` → `is_err_and(|e| e.kind() == ErrorKind::NotFound)` - Tighten the doc comment to state non-`NotFound` errors fail closed - Add two `#[cfg(unix)]` tests pinning the corrected contract: an unverifiable candidate behind a file path component (`ENOTDIR`, deterministic) and a dangling under-root symlink behind a no-search-permission parent (`EACCES`, the case Holmes named) must both be refused Addresses Holmes's review blocker on PR #202. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: 🔒️ Route the "Blade component not found" diagnostic hint through the emit-safe guard. The component-diagnostic loop (main.rs:16683) built its "Expected at:" hint from an unfiltered `possible_paths.first()` — the fourth and last surface in the `from_diagnostic` → `CreateFile` family, still echoing a raw candidate after the view()/@extends/@include loops were closed. A dangling under-root symlink (or out-of-root namespace path) planted at the conventional component path was echoed into the message, parsed back into a create target, passed the lexical-only #199 backstop, and a client `CreateFile` could follow it out of the project tree. Route it through `in_root_expected_path_hint` so all four "Expected at:" surfaces share the emit-safe guard. Also folds in the test hardenings from the same review round: - New `emit_safe_refuses_unverifiable_candidate_behind_a_symlink_loop` pins the `ELOOP` fail-closed case of the emit-safe `None` arm (deterministic on every unix uid), completing the EACCES/ENOTDIR/ELOOP trio named in its contract. - `emit_safe_refuses_out_of_root_candidate` now writes the escapee to disk and asserts the precondition, so it unambiguously proves an *existing* out-of-root file is refused, not merely an absent one. - `expected_path_hint_picks_first_in_root_candidate` gains the `canonicalize().is_err()` precondition matching its sibling tests. - New `component_expected_path_hint_is_unknown_when_all_candidates_out_of_root` regression test mirrors AC #5 for the component surface. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 18, 2026
This was referenced Jul 14, 2026
Merged
Merged
9 tasks done
Merged
7 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2