Skip to content

Consolidate framework-preferences into framework-view with optional presentation plugins #15

Open

Description

@cblanquera

Task

Consolidate stackpress-framework-preferences into stackpress-framework-view, organized as explicit presentation modules with optional host integrations. Preserve framework-ui as the Leptos rendering/component layer.

The owner accepted this direction on 2026-10-28. The goal is a coherent view feature that can include preferences, public flash notices and action-display integration without requiring every consumer to enable persistence or native storage. Renaming the existing bundle to framework-presentation was considered; consolidation into view is the selected direction.

Related: #14 defines the broader function-based framework plugin integration task. This issue specifies the presentation ownership/dependency change; coordinate entry-point naming with #14 rather than implementing competing plugin systems. Its example references to a separate preferences crate should be adapted to this consolidation.

Current behavior and source evidence

Baseline: remote trial/3 at 74319ac366f3397b684ee5fa8007651c19e54292. The cited preferences/language/view sources match the inspected local checkout.

  • Preferences exports combine settings/actions, save state, flash, action-policy helpers and optional native file storage.
  • View manifest currently defines a client-safe page-contract library with optional server integration.
  • Preferences manifest depends on both language and view.
  • Language manifest depends on view; its source imports BasePath and PublicLocation.
  • HostAccess performs authoritative, fail-closed admission as well as public action projection. Moving it must preserve both contracts.
  • Native file storage has explicit ownership/bounds and fixed persistent identities. Moving Rust modules must not reset saved settings.

Current dependencies:

framework-preferences → framework-language → framework-view
                     └────────────────────→ framework-view
framework-ui ──────────────────────────────→ framework-view

Moving preferences unchanged would introduce an invalid view → language → view cycle. Resolve that boundary before consolidation.

Intended organization

Illustrative module layout; final names and feature boundaries need a design checkpoint:

framework-view
├── page / routes        # Public page/render/location contracts
├── preferences          # Locale/theme values, updates and save state
├── flash                # Bounded public notices
├── access               # Public action hints and host-policy integration
└── adapters
    ├── web              # Optional cookie/settings host integration
    └── native           # Optional installed-app settings storage

framework-ui             # Leptos renderers, components and reactive contexts

Illustrative import migration — proposed API, not available today:

// Before:
use stackpress_framework_preferences::{Preferences, PreferenceState, ThemePreference};

// After:
use stackpress_framework_view::preferences::{
    Preferences, PreferenceState, ThemePreference,
};

A crate boundary does not have to equal a plugin boundary. Keep pages available with fixed settings while remembered preferences can be omitted:

// Proposed entry points; ordinary existing ServerBuilder selection.
app.plugin("views", framework_view::plugin::register)?;

#[cfg(feature = "remember-display-settings")]
app.plugin(
    "view-preferences",
    framework_view::preferences::register,
)?;

Use PluginSpec dependency declarations where the reviewed contract identifies a real prerequisite.

Required behavior and boundaries

  • Browser-safe contracts/helpers remain usable without a Server, native filesystem, Tauri or database.
  • Native file persistence and host registration are opt-in. Shared UI consumes public values through adapters; it does not gain filesystem or arbitrary IPC access.
  • Public allowed-action hints control display only. Authoritative admission remains at the protected operation boundary; disabling view/preferences/flash functionality must never bypass it.
  • Cargo feature selection controls compiled code/dependencies; explicit plugin selection controls registered services/handlers/routes. Library import or feature enablement alone must not register effects.
  • Plugin disablement follows the established rebuild/redeploy contract. Omission does not delete cookies, files or other stored settings.
  • Preserve HTTP/Tauri reply formats, cookie names/scope/lifetimes, locale precedence, native file name/revision, cancellation/failure handling and unsaved drafts.
  • No visual redesign, automatic migration/seed/listener startup, new auth system or expanded platform support is requested.

Acceptance criteria

  • Freeze the ownership/module/API/feature plan, including the acyclic locale/view dependency strategy and changes to existing language APIs.
  • Move the selected preferences/flash/access functionality into view without duplicating implementations. UI remains the Leptos layer.
  • Preserve minimal default/browser graphs and optional native/host integration; demonstrate rendering with fixed settings and no preference persistence.
  • Provide ordinary plugin-facing integration consistent with Expose framework features through ordinary Stackpress plugin registration #14; document configuration, policies, route/command exposure and real dependencies.
  • Prove omitted preference/flash integrations add no corresponding registrations or storage writes; compiled-out features cannot be activated through runtime config.
  • Preserve fail-closed protected-operation checks even when presentation integrations are omitted or public action hints are forged.
  • Update workspace members/dependencies, facade exports/features, imports, examples, tests, Rustdoc and relevant specification/KB ownership records. Decide whether to remove the old crate immediately or provide a temporary re-export shim.
  • Prove existing settings remain readable and behavior matches across web settings, flash, installed-app files and host policy, including malformed/duplicate cookies, corrupt files, mounted locations and failed saves.
  • Build relevant native SSR and separate WASM hydrate/CSR graphs; run meaningful behavior tests, doctests and the repository's three review/refactoring rounds. Report coverage and actual platform limitations under the existing 70% development / 90% publication policy.

This issue records the accepted direction. Example APIs/feature names are suggestions until the design checkpoint; it does not request implementation, publication or deployment in this filing task.

Activity

  1. cblanquera commented on Oct 27, 2026

    @cblanquera
    MemberAuthor

    Suggested implementation plan

    The consolidation is accepted; the module names, features and API signatures below are implementation proposals, not existing compile-verified interfaces.

    1. Break the locale/view cycle first

    A practical option is to keep the pure locale registry in framework-language, and move the parts that understand BasePath, PublicLocation, route prefixes, cookies and view settings into view.

    framework-language          # Locale identifiers, aliases and validation
            ↑
    framework-view              # Page/location contracts + presentation integration
            ↑
    framework-ui                # Leptos implementation
    

    Inventory LocaleRegistry methods and PreferenceAction before extraction: some currently combine locale and location behavior. Move those location-aware operations into a view adapter while retaining a single locale-validation authority. Do not add a reverse dependency just to preserve old method paths.

    A possible API boundary:

    // Proposed view API: location integration lives beside view's location contracts.
    // LocaleRegistry remains provided by framework-language.
    pub fn select_preferences(
        registry: &LocaleRegistry,
        scope: &CookieScope,
        location: &PublicLocation,
        cookie: &str,
        default_theme: ThemePreference,
    ) -> Result<Preferences, PreferenceError>;

    The final API must preserve the existing route/cookie/default precedence and encoded mount/query handling. Keeping a shared lower-level location-contract crate is another option if multiple independent consumers justify it, but avoid creating another crate solely to shuffle this cycle.

    2. Move by responsibility, retaining behavior

    Suggested destinations:

    Existing modules Proposed ownership
    action, state, native_settings DTOs view::preferences
    cookie scope/encoding and web settings handlers view::adapters::web, with shared data helpers exposed where needed
    flash view::flash
    policy view::access
    native_file view::adapters::native, optional and target-gated

    Keep data/validation separate from host registration and file IO. Keep framework-ui::state and Leptos contexts in UI; they adapt public presentation values into reactive state.

    For access, keep both the display projection and actual admission API explicit:

    // Proposed new module path; same existing fail-closed semantics.
    use stackpress_framework_view::access::HostAccess;
    
    // A UI AllowedActions projection is never the authority for this call.
    HostAccess::new(policy, &fresh_host_context)
        .run(&action_id, || protected_work())?;

    The host still supplies identity/resource/Origin/CSRF decisions. A compile-time-disabled presentation feature must not select an unchecked implementation of protected work.

    3. Separate pure features from host adapters

    Illustrative additions to the view manifest, retaining its existing server feature:

    [features]
    default = []
    server = ["dep:stackpress-lib", "dep:stackpress-server"]
    preferences = ["dep:stackpress-framework-language", "dep:url"]
    flash = []
    web-preferences = ["preferences", "server"]
    web-flash = ["flash", "server"]
    native-file = ["preferences", "dep:libc"]
    
    [dependencies.stackpress-framework-language]
    path = "../framework-language"
    optional = true
    
    [dependencies.url]
    version = "=2.5.8"
    optional = true
    
    [target.'cfg(unix)'.dependencies.libc]
    version = "=0.2.191"
    optional = true

    This is a feature-boundary sketch, not a complete manifest. Resolve actual shared cookie/flash dependencies explicitly; target-gate existing native/server modules and retain the current Unix-only file implementation limitation.

    Browser consumers can select pure preferences/flash data without server/native-file features. Native consumers may use file storage directly without registering web handlers. Test separate target/feature graphs: feature names do not themselves prove isolation, and Cargo features are additive.

    4. Define plugin registration independently from module ownership

    Possible application code after the registration contract is frozen:

    use stackpress_server::{PluginSpec, ServerBuilder};
    use stackpress_framework_view as framework_view;
    
    // The application's manifest selects the corresponding native integration features.
    let mut app = ServerBuilder::new();
    app.plugin("views", framework_view::plugin::register)?;
    
    #[cfg(feature = "remember-display-settings")]
    app.plugin_with(
        // Declare this only if preference integration actually needs the views plugin.
        PluginSpec::new("view-preferences").requires(&["views"]),
        framework_view::preferences::register,
    )?;

    Choose/document what the base view plugin actually registers; avoid an empty registration function for appearance. Expose web and native-specific registration separately if one entry point would hide transport policy.

    Apply configuration before build and validate during startup. Do not automatically expose generic event dispatch. Reuse existing lifecycle/page registration; do not introduce a second dispatcher or start listeners from view startup.

    5. Migrate imports without changing stored data

    Update real consumers: ui-preferences shared/client/web, Profile UI client/native, root facade/features and any examples/docs found by a fresh search. Preserve stackpress-locale, stackpress-theme, stackpress-flash, ui-preferences-v1.json, existing revisions and their serialization/persistence contracts.

    If a temporary compatibility crate is useful, make it depend only toward the new owner:

    // Temporary framework-preferences shim, if selected.
    // Its manifest enables the new view preference/flash features.
    pub use stackpress_framework_view::preferences::*;
    pub use stackpress_framework_view::flash::*;
    pub use stackpress_framework_view::access::*;

    Account explicitly for old CookieScope/native_file paths and forward native-file if compatibility is promised. Never let view depend back on the shim. The language API migration is separate: wrappers there cannot depend on view if view now depends on language. Document deliberate Rust API changes instead of recreating a cycle.

    6. Verify in bounded stages

    First prove the language extraction, then module migration, then optional registration and example wiring. Use behavior-focused before/after checks:

    • Existing cookies/files decode identically; first render and refresh retain locale/theme.
    • Preference confirmation remains read-only; a failed/unconfirmed write is not described as rollback.
    • Corrupt files, bounded reads, late file-sync failures and exclusive directory ownership retain their contracts.
    • Flash remains bounded and best-effort across tabs, not exactly-once.
    • Plugin omission leaves pages usable with fixed settings, creates no preference routes/services/writes, and leaves protected operations fail closed.
    • Missing actual prerequisites, duplicates and invalid configuration reject startup appropriately.
    • Mounted routes, encoded queries, hydration seeds and dirty form values remain consistent.
    • Native SSR, separate WASM hydrate/CSR and appropriate native-file checks pass without browser-native dependency leakage.

    Run the required complexity, responsibility/abstraction and consistency/Rustdoc rounds with meaningful test verification. Update the architectural ownership documents after the API is settled. Report existing desktop/mobile proof limits rather than claiming new target support from this rearrangement.

  2. cblanquera commented on Oct 27, 2026

    @cblanquera
    MemberAuthor

    Coordination update: reusable Leptos ownership

    See #17: the owner now requests extracting framework-ui's reusable Leptos functionality into crates/leptos, with framework-view consuming it through optional integration.

    The accepted consolidation in this issue remains: host preference selection/persistence, public flash policy and action-policy integration move into view. The Leptos contexts/components remain a distinct responsibility, now owned by the independent stackpress-leptos library.

    The earlier suggested diagram with framework-ui → framework-view is superseded by the intended edges below (arrows mean "depends on"):

    framework-view → framework-language      # After removing language's reverse edge
    framework-view → stackpress-leptos       # Optional rendering/UI integration
    Independent app → stackpress-leptos      # No view/server dependency
    

    There are two cycles to avoid: this issue's existing language → view edge, and #17's existing UI → view edge. Resolve both in the shared ownership plan before reversing dependencies.

    Use explicit adapters for public settings. For example, proposed post-migration imports:

    use stackpress_framework_view::preferences::ThemePreference;
    use stackpress_leptos::state::Theme;
    
    // View/application adapter converts persistent preference into generic UI input.
    let ui_theme = match preferences.theme {
        ThemePreference::Light => Theme::Light,
        ThemePreference::Dark => Theme::Dark,
        ThemePreference::System => Theme::System,
    };

    The generic UI library must not import view's preference types or read cookies/native files. It receives initial values and callbacks; view/host code validates, persists and publishes acknowledged values. Preserve System mode, before-paint/hydration agreement, failed-save drafts, and authoritative host checks.

    Update the migration map, facade/features, compatibility strategy and independent-consumer tests jointly with #17. Do not keep the old framework-ui dependency direction just to preserve examples; preserve behavior while making API/path migration explicit.

  3. added 9 commits that reference this issue on Oct 29, 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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions