Skip to content

Expose framework features through ordinary Stackpress plugin registration #14

Open

Description

@cblanquera

Problem

Framework features should be consumable through the same function-based Stackpress plugin mechanism used by application and third-party plugins. Today the framework crates are reusable libraries, but host integration is uneven: application code assembles page registration, settings handlers, generated operations and generation tooling through different APIs.

The requested outcome is a coherent plugin-facing integration layer over those libraries. This does not require one plugin per crate, a plugin trait, or a new plugin loader.

Current behavior and source evidence

Review baseline: remote trial/3 at 74319ac366f3397b684ee5fa8007651c19e54292. The cited integration sources match the inspected local checkout; unrelated later admin edits are outside this issue.

Existing registration shape:

// Existing host API; "register" is an ordinary function pointer.
pub fn plugin(
    &mut self,
    id: &str,
    register: fn(&mut ServerBuilder) -> stackpress_lib::Result<()>,
) -> stackpress_lib::Result<&mut Self>;

Current composition excerpt from the Profile UI application:

backend::register(app)?;
register_pages_with_module(app, base.clone(), module)?;
app.post(
    routes::search_api(&base).map_err(Error::from_source)?.as_str(),
    search_api,
)?;

Requested behavior

Allow consumers to select framework features through ordinary app.plugin(...) / app.plugin_with(...) registration while retaining direct use of the underlying libraries.

Illustrative consumer experience — proposed names, not existing APIs:

use stackpress_lib::Result;
use stackpress_server::ServerBuilder;

fn presentation(app: &mut ServerBuilder) -> Result<()> {
    stackpress_framework_preferences::plugin::register(app)?;
    stackpress_framework_view::plugin::register(app)?;
    // Application-specific pages, renderers and policies remain explicit.
    Ok(())
}

async fn application() -> stackpress_server::BuildResult<stackpress_server::Server> {
    let mut app = ServerBuilder::new();
    app.plugin("presentation", presentation)?;
    app.build().await
}

The issue concerns developer integration. It does not request a visual redesign or changed search/CRUD behavior.

Scope and boundaries

Assess all seven current framework crates: view, UI, language, preferences, schema, SQL and admin. Decide which own runtime registration, which contribute to a composite feature, and which remain support libraries or build-time facilities. Avoid empty registration functions added solely for naming consistency.

Preserve compile-time selection, ordinary lifecycle/dispatch semantics, explicit application policy and separate web/native adapters. Importing/enabling a library must not expose routes automatically. Plugin startup must not implicitly install/migrate/seed databases or start network listeners.

Keep Idea/code-generation registration in the build tool; do not pull generators into the final application or native services into browser builds. Runtime registration and build-time participation need separate documented entry points where applicable.

Acceptance criteria

  • Record a reviewed capability-to-crate/plugin map for all seven crates, including registration responsibilities, required configuration/services, dependency order, supported targets and deliberate exclusions.
  • Provide documented function-based entry points for the selected host-integrated features, compatible with existing ServerBuilder plugin APIs; preserve directly usable libraries.
  • Publish a responsibility list for each entry point: configuration, startup callbacks, services, operations, routes and optional build-tool hooks. Application-owned policies and route/model selections remain explicit.
  • Demonstrate both a directly selected framework plugin and application composition/customization without changing core handlers or introducing a second dispatcher.
  • Update at least one existing presentation example to use the integration and preserve its public responses, locale/theme behavior, route mounts and lifecycle behavior.
  • Prove meaningful negative behavior: missing/wrong-order dependencies, duplicate/conflicting registrations, invalid configuration and denied protected actions fail through the existing contracts before inappropriate effects.
  • Verify disabled integrations register nothing and preserve appropriate default/browser/native dependency graphs. Build SSR, hydration and CSR separately.
  • Document the migration from existing application wiring, direct-library usage, feature selection and runtime-versus-generation integration. Run relevant tests, doctests, target checks and the repository's three substantive review/refactoring rounds; report coverage and platform limitations under the existing quality policy.

Non-goals

Dynamic loading/hot enablement, a universal plugin trait, mandatory directory layouts, implementing missing authentication/synchronization systems, dependency/toolchain upgrades, publication or deployment. This issue records work to design and implement; the snippets do not freeze the final API.

Activity

  1. cblanquera commented on Oct 27, 2026

    @cblanquera
    MemberAuthor

    Suggested implementation approach

    These are proposals for the design checkpoint. All new symbols in the snippets below are sketches, not compile-verified APIs.

    1. Map features before adding entry points

    Keep the current pure libraries and choose plugin boundaries by behavior:

    Current crate Suggested role to assess
    framework-view Optional host integration for page registration/rendering policy; keep page contracts pure.
    framework-ui Leptos components/renderers selected by presentation integration; no artificial runtime plugin just to import components.
    framework-language Locale registry/selection support, possibly composed with presentation preferences.
    framework-preferences Optional settings integration; keep cookie/file adapters and host policy explicit.
    framework-schema Runtime contracts plus separate build-time normalization/generation hooks.
    framework-sql Optional runtime service/selected generated-operation integration; separate migration tooling.
    framework-admin Selected generated page/operation integration, consuming the existing SQL/schema/UI layers.

    Prefer an optional plugin module behind the crate's existing server feature where appropriate. If that creates an awkward dependency graph, justify a separate integration crate instead. Do not make every pure helper depend on stackpress-server.

    2. Reuse the existing registration contract

    For independently identified features, use PluginSpec rather than inventing a registry or loader. The IDs, dependencies and entry-point names here are illustrative and require the responsibility map first:

    use stackpress_server::{PluginSpec, ServerBuilder};
    
    // Proposed feature entry points, using the existing host selection API.
    app.plugin("preferences", preferences::plugin::register)?;
    app.plugin_with(
        PluginSpec::new("presentation").requires(&["preferences"]),
        presentation::register,
    )?;

    Only declare a dependency if the feature actually requires that selected plugin. Keep the caller's ordering and existing missing/cycle/order failures.

    For a composite feature, call contribution helpers inside one selected function. Do not call app.plugin(...) from a registration callback: the current builder seals plugin selection before callbacks run. Helpers executed under a composite retain that composite's identity; use separate selections when distinct provenance/order requirements matter.

    use stackpress_lib::Result;
    use stackpress_server::ServerBuilder;
    
    // Proposed helpers; these contribute registrations, not new plugin selections.
    fn presentation(app: &mut ServerBuilder) -> Result<()> {
        preferences::plugin::register(app)?;
        pages::register(app)?;
        Ok(())
    }
    
    // Select the composite at the application boundary, before build().
    app.plugin("presentation", presentation)?;

    3. Keep selection at compile time and configuration typed

    A possible native consumer manifest:

    # Local paths are relative to this consumer. Published versions are not assumed.
    [features]
    default = []
    presentation = [
        "dep:stackpress-framework-view",
        "dep:stackpress-framework-preferences",
    ]
    
    [dependencies]
    stackpress-server = { path = "../rust/crates/server" }
    [dependencies.stackpress-framework-view]
    path = "../rust/crates/framework/framework-view"
    optional = true
    features = ["server"]
    
    [dependencies.stackpress-framework-preferences]
    path = "../rust/crates/framework/framework-preferences"
    optional = true
    features = ["server"] # Proposed new optional integration feature.
    #[cfg(feature = "presentation")]
    app.plugin("presentation", presentation)?;

    Keep the frontend as a separate hydrate/CSR consumer with host features disabled. Use typed configuration sections and startup callbacks to validate options and construct services. Register application settings before build(); do not depend on ambient globals or runtime strings loading compiled-out code. The existing function-pointer API needs no closure capture: configuration, application wrapper functions and contribution helpers cover the ordinary cases.

    4. Share operations, keep transport policy in adapters

    Website HTTP adapter ─┐
    Tauri command adapter ├→ selected plugin handlers → shared operations/services
    CLI adapter ──────────┘
    

    Presentation integration should not silently expose arbitrary event names over HTTP/IPC. A settings plugin needs explicit routes/command mapping, origin/CSRF/authorization policy where applicable, cookie scope or native storage policy, and safe public projection.

    Reuse ServerBuilderViewExt::page, PageSpec, the Leptos renderer and generated operation handlers. Keep normal plugin startup separate from database installation/migration/seed commands and explicit network-host startup.

    5. Separate build-tool participation

    A feature may provide both runtime integration and a generator, but use them in different binaries/build graphs:

    // Build-tool binary; proposed convenience wrapper around existing transform APIs.
    framework_feature::codegen::register(&mut transform_registry)?;
    
    // Final native application; no parser/transformer required by normal dependencies.
    app.plugin("feature", framework_feature::plugin::register)?;

    Do not force the transform registry to implement ServerBuilder registration or conflate generation with runtime startup.

    6. Deliver a bounded pilot, then extend

    Start with a presentation/preferences vertical and one existing example. Freeze its responsibility/configuration contract, prove parity and negative cases, then apply the convention to SQL/admin where the map identifies useful integration.

    Suggested behavior-focused checks:

    • Plugin-selected versus current registration produces equal public output for normal, empty and error paths.
    • A request records one top-level lifecycle; nested delegation does not restart it.
    • Explicit application policy denies protected behavior, including forged UI action projections.
    • Invalid/missing services and duplicate page/service claims reject startup without a usable partial host.
    • Unselected code adds no services/listeners/routes; enabling a Cargo library alone does not register it.
    • Mounted routes, persistence failure and unsaved drafts retain existing behavior.
    • Browser normal dependencies exclude native host/SQL/Tauri/generation code; final native runtime excludes build-tool dependencies.
    • Use in-process/buffered/duplex proofs where possible. Report actual desktop/mobile validation separately; do not infer packaged support from compilation.

    Complete the repository's complexity, responsibility/abstraction and consistency/Rustdoc review rounds with meaningful tests. Keep direct-library examples alongside the plugin example so the added integration does not become mandatory.

  2. cblanquera commented on Oct 27, 2026

    @cblanquera
    MemberAuthor

    Coordination update: independent Leptos library and view plugin

    See #17, requested after this issue was filed: extract the reusable part of framework-ui into crates/leptos (intended package stackpress-leptos) and make framework-view consume it through optional integration.

    This refines the capability-to-crate/plugin map above:

    • stackpress-leptos is an ordinary reusable library. It must not require a Stackpress host/plugin, page protocol or preference persistence merely to use components.
    • framework-view owns the Stackpress-facing page/route/render adapters and plugin registration.
    • Consolidate framework-preferences into framework-view with optional presentation plugins #15 consolidates host-owned preferences/flash/access integration into view; it does not move their persistence or authority into the generic Leptos library.
    • Independent component use must work without view/server; view's base contracts must remain usable without Leptos features.

    Proposed usage examples, not APIs available today:

    // Independent Leptos consumer:
    use stackpress_leptos::components::{Input, Popover};
    // Stackpress consumer; ordinary host registration:
    app.plugin("views", stackpress_framework_view::plugin::register)?;

    Update the proposed registration map and old framework-ui-* facade/feature examples in light of #17. Do not add a UI plugin that exists only to import components. Preserve direct library use, compile-time selection and explicit runtime registration. Break the current UI → view dependency before adding view → reusable Leptos integration; a reverse-edge feature would still create a cycle.

    Coordinate the final entry points with #15/#17, and use #16's server-family paths when those changes land. Earlier code sketches remain historical proposals, not additional APIs to implement in parallel.

  3. added 10 commits that reference this issue on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions