diff --git a/AyCode.Core/Consts/AcEnv.cs b/AyCode.Core/Consts/AcEnv.cs
index c421026..990ed64 100644
--- a/AyCode.Core/Consts/AcEnv.cs
+++ b/AyCode.Core/Consts/AcEnv.cs
@@ -34,6 +34,32 @@ namespace AyCode.Core.Consts
set => _baseUrl = value;
}
+ ///
+ /// A szerver ADAT-környezete: a mögöttes adatbázis PROD-e. FIGYELEM: NEM build-konfiguráció (Debug/Release)
+ /// és NEM hosting-környezet (ASPNETCORE_ENVIRONMENT) — három független tengely; ez az adat-tengely.
+ /// Forrás: a szerver a DB-katalógusából sorolja be magát (fail-closed); a kliens tükrözi (kapcsolat-felépüléskor
+ /// a szerver env-válaszából + push-on-connect announce). Fail-fast: hidratálatlan olvasás exception.
+ ///
+ private static bool? _isProdDb;
+ public static bool IsProdDb
+ => _isProdDb ?? throw new InvalidOperationException(
+ "AcEnv.IsProdDb nincs hidratálva — a szerver a DB-katalógusból tölti induláskor; a kliens a kapcsolat felépülésekor a szerver env-válaszából.");
+
+ /// True, ha az IsProdDb már hidratált — kapu-logikához (első-connect await) és a push-összevetéshez.
+ public static bool IsProdDbHydrated => _isProdDb != null;
+
+ /// Csak TÉNYLEGES változáskor tüzel (első hidratálás + announce-detektált env-váltás) —
+ /// a megjelenítők (title-badge, UI-sáv) feliratkozási pontja.
+ public static event Action? IsProdDbChanged;
+
+ public static void SetIsProdDb(bool isProdDb)
+ {
+ var changed = _isProdDb != isProdDb;
+ _isProdDb = isProdDb;
+
+ if (changed) IsProdDbChanged?.Invoke(isProdDb);
+ }
+
private static IConfiguration? _appConfiguration = null;
public static IConfiguration AppConfiguration => _appConfiguration ??= GetAppSettingsConfiguration();
diff --git a/AyCode.Services/SignalRs/README.md b/AyCode.Services/SignalRs/README.md
index 7b2cfc2..c815ede 100644
--- a/AyCode.Services/SignalRs/README.md
+++ b/AyCode.Services/SignalRs/README.md
@@ -27,3 +27,13 @@ Custom binary SignalR protocol, client infrastructure, message tagging, and seri
### Serialization & Pooling
- **`SignalRSerializationHelper.cs`** — Static helpers: `SerializeToBinary()`, `DeserializeFromBinary()`, compressed JSON variants.
- **`SignalRRequestModel.cs`** — Poolable (`IResettable`) request tracking model with `ObjectPool` for reuse.
+
+### Data Source
+
+> **Full specification:** `AyCode.Services/docs/SIGNALR_DATASOURCE/README.md`
+
+- **`AcSignalRDataSource.cs`** — Abstract generic real-time collection (`AcSignalRDataSource`) implementing `IList`, `IList`, `IReadOnlyList`. Consumer of the transport above (not part of it) — wraps CRUD-tag communication behind a regular list. Transport-agnostic by design; the current implementation is SignalR-coupled.
+ - **Change tracking:** `TrackingItem` wraps each modified item with `TrackingState` + `OriginalValue` (rollback snapshot). `ChangeTracking` manages the tracked list.
+ - **Loading:** `LoadDataSource()` (sync-wait), `LoadDataSourceAsync()` (async callback), `LoadItem(id)` (single). Raw-`byte[]` path uses `AcBinaryDeserializer.PopulateMerge` into `IAcObservableCollection` (batch UI notification via `BeginUpdate`/`EndUpdate`); `SerializerType` selects Binary vs JSON+GZip.
+ - **Saving:** `SaveChanges()` / `SaveChangesAsync()` iterate tracked items, post each via the state's CRUD tag, roll back on failure; `SaveItem()` for individual saves. `IsSyncing` + `OnSyncingStateChanged` expose sync state.
+- **`TrackingItemHelpers.cs`** — `JsonClone` / `ReflectionClone` deep-clone helpers producing the `OriginalValue` rollback snapshots.
diff --git a/AyCode.Services/docs/SIGNALR_DATASOURCE/SIGNALR_DATASOURCE_TODO.md b/AyCode.Services/docs/SIGNALR_DATASOURCE/SIGNALR_DATASOURCE_TODO.md
index cee50dd..a7dd3ce 100644
--- a/AyCode.Services/docs/SIGNALR_DATASOURCE/SIGNALR_DATASOURCE_TODO.md
+++ b/AyCode.Services/docs/SIGNALR_DATASOURCE/SIGNALR_DATASOURCE_TODO.md
@@ -2,6 +2,8 @@
Forward-looking work specific to `AcSignalRDataSource`. Transport-side TODOs live in `../SIGNALR/SIGNALR_TODO.md`.
+> **Server-side filtering — design in ADR 0004.** The client→server filter mechanism (`SignalParams.FilterText` + `[SignalRFilterText]` hub injection + filter-service + per-entity whitelist) is captured in [`0004-signalr-datasource-server-side-filtering.md`](../../../docs/adr/0004-signalr-datasource-server-side-filtering.md) (Status: Proposed). Not yet broken into per-step entries — reference the ADR when implementation begins.
+
## Priority legend
- **P0** blocker · **P1** important · **P2** nice-to-have · **P3** idea
diff --git a/docs/adr/0002-environment-infrastructure-to-framework.md b/docs/adr/0002-environment-infrastructure-to-framework.md
new file mode 100644
index 0000000..bf3e560
--- /dev/null
+++ b/docs/adr/0002-environment-infrastructure-to-framework.md
@@ -0,0 +1,35 @@
+# ADR 0002: Environment infrastructure lifted to the framework (BaseUrl, server data-environment, env badge) — cross-reference
+
+## Status
+
+Accepted (2026-07-15) — cross-reference; the full decision record lives in the consumer repo (see below).
+
+## Cross-reference
+
+**Primary ADR:** `FruitBankHybridApp/docs/adr/0002-environment-infrastructure-to-framework.md`
+([relative link](../../../../../Mango/Source/FruitBankHybridApp/docs/adr/0002-environment-infrastructure-to-framework.md) — workspace layout dependent)
+
+Same NNNN as the primary by design (adr-author skill, multi-repo rule: "under the same NNNN if possible") —
+this is why 0002 is used here instead of max+1.
+
+## Scope on this repo's side
+
+The decision moves app-agnostic environment infrastructure from the consumer (FruitBank) into this framework,
+executed stepwise (per-step review):
+
+- **AyCode.Core** — `AcEnv.BaseUrl` (✅ done, 2026-07-15) and `AcEnv.IsProdDb` (+ `Set`/`Hydrated`/`Changed` event; planned):
+ single-source, fail-fast state; axis-separated naming (`IsProdDb` = data environment ≠ `IsProdBuild` = build config).
+- **AyCode.Services** — framework-reserved tags in `AcSignalRTags` (query 90020 / announce 90021, GridLayout-roaming
+ precedent) + environment machinery in `AcSignalRClientBase` (first-connect gate, `EnsureEnvironmentAsync`,
+ fail-closed raw query, announce interception; **opt-in** via `EnvironmentFeatureEnabled => false` default).
+- **AyCode.Services.Server** — hub-base push-on-connect announce from `AcEnv.IsProdDb` (best-effort).
+- **AyCode.Blazor.Components** — `IAcAppTitleService` + browser implementation + `` component.
+
+Consumer-side remains: domain constants, DB-catalog classification call-site, query endpoint implementation
+(framework tag), MAUI title implementation, app name. Consumer facades delegate to `AcEnv` (zero-break migration).
+
+## Related
+
+- Related ADRs: primary record (see Cross-reference above).
+- Related TODOs/Issues: `ACCORE-LOG-T-N2D8` (AcEnv.SetConfiguration synergy), `ACCORE-LOG-T-D7S3`,
+ `ACCORE-SBP-T-T5R2`, `ACBLAZOR-GRID-T-E3B6`.
diff --git a/docs/adr/0004-signalr-datasource-server-side-filtering.md b/docs/adr/0004-signalr-datasource-server-side-filtering.md
new file mode 100644
index 0000000..d7ac0a8
--- /dev/null
+++ b/docs/adr/0004-signalr-datasource-server-side-filtering.md
@@ -0,0 +1,89 @@
+# ADR 0004: Server-side filtering for SignalR DataSource via DevExpress CriteriaOperator string + `[SignalRFilterText]` hub injection
+
+## Status
+
+Proposed (2026-07-15)
+
+> **Note:** Proposed / refinable. The design was settled across a multi-turn design discussion (this ADR captures it in place of the `adr-author` interview). Implementation-level details (filter-service whitelist specifics, function-translation caveats) are expected to sharpen during execution and this ADR will be updated.
+
+## Context
+
+MgGrid grids currently load the **full** dataset from the server (`GetAll`) into an `AcObservableCollection`, bound directly to DevExpress `DxGrid.Data`; DevExpress then filters / sorts / pages **in the browser** over the whole set. For large tables (thousands / tens-of-thousands of rows) this is inefficient. FruitBank works around it with a hardcoded **15-day** server window (`Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/CustomOrderSignalREndpoint.cs`), which also blocks access to older data.
+
+Current state (verified):
+- `MgGridSignalRDataSource : GridCustomDataSource` exists but is **unwired** (zero instantiation sites); live grids (`FruitBankGridBase` → `MgGridBase`) bind the full collection to `DxGrid.Data`.
+- The client already produces the DevExpress `CriteriaOperator` (used for local filtering, and as a cache key via `CriteriaOperator.ToString()`).
+- The transport already carries `FilterText` (string) + `ContextIds` (object[]) with every `GetAll` (`AcSignalRDataSource.GetContextParams()`). `ContextIds` is the live mechanism (domain-scoped typed params); `FilterText` is latent / unused in FruitBank.
+- A prior **expression-based** server filter exists but is **test-only**: `AcExpressionNode` + `AcSerializerCommon.ApplyQueryFromNode` / `ExecuteQueryFromNode` serialize and execute arbitrary LINQ expression trees server-side (including arbitrary `Expression.Call` to `Queryable.Count`/`Sum`/`Any`).
+
+Timing — decide now: filtering must move server-side to scale grids and unlock all-data access, AND the expression path (a remote-code-execution-shaped injection surface) must be replaced with a safe mechanism before more grids are built on it.
+
+**In scope:** the universal client→server filter mechanism for the SignalR DataSource + MgGrid — **v1 = filtering**.
+**Out of scope:** server-side **paging** (v2 — `skip`/`take`/`count`); full removal of `AcExpressionNode` from the serializers (separate effort); the change-tracking / rollback model (untouched — v1 stays in the load-all model, just server-filtered).
+
+## Decision
+
+Adopt a **criteria-string** server-side filter for the SignalR DataSource. The DevExpress grid filter travels as a `CriteriaOperator` **string** (bounded query DSL), applied server-side to `IQueryable`; `AcExpressionNode` is **not** used for grid filtering.
+
+| Concern | Decision | Layer |
+|---|---|---|
+| Filter representation | DevExpress `CriteriaOperator` **string** (`ToString()` client → `Parse()` server), NOT a serialized expression tree | — |
+| Wire carriage | Rides in the always-sent `SignalParams` metadata (`SignalParams.FilterText`), NOT a positional method param | AyCode.Services (`ISignalParams`) |
+| Server binding | Hub (`AcWebSignalRHubBase`) injects `SignalParams.FilterText` into a param marked `[SignalRFilterText]`, resolved **once** from the cached `AcMethodInfoModel.ParamInfos` (zero per-call reflection) | AyCode.Services.Server |
+| Mismatch diagnostic | If a `FilterMode.Server` grid sends `FilterText` but the method has **no** `[SignalRFilterText]` param → **warning-log** the mismatch: a real misconfiguration (grid declared `Server`, endpoint can't filter). **Precise** — never fires for `FilterMode.Local` grids. Log level / per-tag suppression is an implementation decision | AyCode.Services.Server |
+| Application | Shared **filter-service**: parse → **per-entity field whitelist** → apply to the endpoint's `IQueryable` **before** `.ToListAsync()` (SQL WHERE). Requires `DevExpress.Data` (non-UI) on the server | AyCode.Services.Server |
+| Client forwarding (`FilterMode`) | MgGrid carries a `FilterMode` enum (**Local** / **Server**). `Server`: the grid forwards its `CriteriaOperator` into `SignalParams.FilterText` and a filter change triggers a server reload. `Local`: the grid filters the loaded set client-side — no forward, no round-trip (today's behavior). `FilterMode` is a **grid-level behavioral policy (intent)**, NOT a client-side claim about server capability — this is the distinction from the rejected `HasFilterTextParam`, so it does not reintroduce that drift. The server's cached reflection stays the single authority on *capability*; `FilterMode` decides *participation*. Default = user decision (lean `Local`) | AyCode.Blazor |
+| Rollout | Gradual, per-endpoint opt-in: add `[SignalRFilterText] string? filterText` + one filter-service call. No endpoint-signature-position fragility, no per-endpoint duplication of filter logic | consumer plugin |
+| Scope | **v1 = filtering** (load-all model, change-tracking untouched). **v2 = paging** (`skip`/`take`/`count` + `{items,total}` envelope) extends the same `SignalParams` channel later | — |
+
+## Consequences
+
+**Positive:**
+- **Bounded to a WHERE predicate — no RCE.** Replaces the `AcExpressionNode` surface (which executed arbitrary server-side expression trees) with a criteria DSL that can only express a filter predicate over the queried entity. The worst a tampered filter string can do is **add / remove / widen filter conditions over the same query** — not execute code. (The residual "which columns / relations / functions" surface is closed by the whitelist — see Negative.)
+- Wire format is a plain string → vendor-neutral on the wire, fits the existing `FilterText` channel; the framework stays DevExpress-free on the wire.
+- **Universal + gradual**: the injection logic lives **once** in the hub; endpoints opt in with an attribute + one service call. No positional-param fragility (the `ContextIds` object[] is variable-length).
+- **Single source of truth**: the server's cached reflection is authoritative on filter support; no drift-prone client-side flag duplicating it.
+- **Loud, not silent, precise**: a `FilterMode.Server` grid whose endpoint lacks `[SignalRFilterText]` triggers a server warning (a real misconfiguration signal), never a silent no-filter drop — and `FilterMode.Local` grids never false-trigger it.
+- Efficient: filter applied to `IQueryable` before materialization → SQL `WHERE`.
+- Unblocks large grids and (with v2) all-data access, retiring the 15-day hack.
+- Change-tracking untouched in v1 (still load-all, just a smaller server-filtered set).
+
+**Negative:**
+- Adds a `DevExpress.Data` dependency to the server plugin (currently DevExpress-free server-side; it has **DevExtreme**, a different product line). `DevExpress.Data` is UI-agnostic and runs in the Razor/MVC plugin — not a Blazor dependency.
+- **Field-whitelist is mandatory — DevExpress does NOT provide it.** The criteria DSL bounds the *shape* to a WHERE predicate (no arbitrary code — see Positive), but `CriteriaOperator` places **no restriction on which columns / relations / functions** a criteria may reference. Within the WHERE surface a tampered filter string can still: (a) filter on a non-displayed / sensitive column as a **blind-injection oracle** (`[PasswordHash] LIKE 'a%'` → rows-or-not leaks the value character by character); (b) navigate **relations** (`[Customer.CreditCard]`) into other tables; (c) use expensive functions / aggregate subqueries (**DoS**). The per-entity whitelist (allowed filterable columns + relation depth + function set; un-translatable features rejected, not client-eval'd) is the boundary DevExpress leaves to us.
+- Opt-in discipline (narrowed): a **missing** `[SignalRFilterText]` attribute is now caught by the mismatch warning; but an attribute **present with the injected value unused** (developer forgot the filter-service call) is still a silent no-filter.
+- **One grid-level policy knob (`FilterMode`)**: minimal ceremony (an enum with a sensible default), but a genuine config surface. It is grid *intent*, not a server-capability claim, so it does not reintroduce the `HasFilterTextParam` drift; and it removes the false-positive warning for intentionally local grids.
+- SQL-translation caveats: some criteria functions may not translate via linq2db → **function whitelist**; complexity / timeout caps needed (leading-wildcard `Contains` on a large unindexed table = full scan / DoS); parse failure must **hard-reject** (never "no filter → return everything").
+- `MgGridSignalRDataSource` (currently unwired) needs finishing, and `MgGridBase` diverting off direct `DxGrid.Data` binding onto it — but only for the eventual ServerPaged case; **v1 filtering can ride the existing load path** (server returns a smaller filtered set into the same collection).
+
+**Follow-ups required:**
+- File SIGDS + GRID TODO entries: `[SignalRFilterText]` attribute + hub injection + **mismatch warning-log** (AyCode.Services.Server); `SignalParams.FilterText` field (AyCode.Services); filter-service + per-entity whitelist (AyCode.Services.Server); MgGrid `FilterMode` (Local / Server) + criteria serialization into `SignalParams.FilterText` on `Server` (AyCode.Blazor); add `DevExpress.Data` to the server plugin.
+- **Enabled extension** (own follow-up, not core to this ADR): MgGrid declarative **quick-filter templates** — field-bound `DxTagBox` (or similar) slots in the grid container that auto-populate from distinct values, produce a `CriteriaOperator` merged into the grid filter, and ride the same `FilterMode` path. Needs a server distinct-values source under `FilterMode.Server` (see v2 paging).
+- Verify: `DevExpress.Data` version matching the client DevExpress Blazor major; license coverage on the server package feed; linq2db translation of `CriteriaToExpressionConverter` output (function whitelist).
+- **Quick-search (`SearchText`) — open / questionable point:** DevExpress `SearchText` (the search box above the grid) is a **separate feature** from `FilterCriteria`, currently filtering locally over the bound in-memory `Data`. Whether DevExpress folds it into `GridCustomDataSourceItemsOptions.FilterCriteria` (→ search rides the criteria-string path for free) or needs a thin `SearchText → OR-Contains criteria` adapter over searchable columns is **unverified** — confirm at implementation. Same whitelist / DoS caveats apply (search = OR-Contains across many columns).
+- AyCode.Blazor cross-ref: no `docs/adr/` folder there yet → note in MGGRID docs or a future AyCode.Blazor ADR.
+- v2 paging ADR (`skip`/`take`/`count` + `{items,total}` envelope) when large-list pressure requires it.
+- `AyCode.Core/docs/adr/README.md` index row for 0004.
+
+## Alternatives considered
+
+- **`AcExpressionNode` (serialized expression tree)** (rejected): serialize the DevExpress filter as a full LINQ expression tree and execute server-side (the existing test-only path). Rejected — executes arbitrary client-supplied `Expression.Call` server-side (RCE-shaped injection); sandboxing arbitrary expression trees is effectively intractable. Being deprecated independently.
+- **DevExtreme `DataSourceLoader`** (rejected for the Blazor grids): the server already references `DevExtreme.AspNet.Data`, with a commented-out `DataSourceLoader.Load` prototype (`ShippingController.cs:110-127`).
+ - *Fit:* DevExtreme is the JS-widget product line for the nopCommerce **admin Razor** grids — a different UI stack from the FruitBank **Blazor WASM** app grids.
+ - *Cost:* using it for the Blazor grids means translating the DevExpress-Blazor `CriteriaOperator` model into DevExtreme `loadOptions` — an impedance mismatch. Reasonable for the nop admin Razor grids; wrong for the Blazor app grids.
+- **Positional `string? filterText` parameter per endpoint** (rejected): append `filterText` as a trailing positional param.
+ - *Cons:* with a variable-length `ContextIds` object[], "which position" is fragile; and it is per-endpoint signature editing. The `SignalParams` + attribute-injection removes both problems.
+- **Naming convention (`filterText` param name) instead of an attribute** (rejected in favor of the attribute): bind by a consistently-named param + developer discipline. Rejected — silent failure on typo / rename; `[SignalRFilterText]` is self-documenting, rename-safe, and its param index precomputes into the cached method model.
+- **Client-side opt-in flag (`HasFilterTextParam` on MgGrid)** (rejected): a per-grid bool **claiming** whether the endpoint has the filter param. Rejected — duplicates the server's authoritative cached reflection (drift-prone), and a forgotten flag = silent no-filter. Superseded by the `FilterMode` (Local / Server) **policy** enum + server mismatch warning: `FilterMode` declares the grid's own intent (not a server claim), the server's reflection stays the single authority on capability, and a `Server`-declared grid hitting an unsupported endpoint surfaces as a precise warning. The distinction is intent-vs-capability-claim, not "flag vs no-flag".
+- **Framework choke-point auto-applies to `IQueryable` results** (deferred, not chosen for v1): the hub inspects the method result and, if `IQueryable`, applies filter + page + count generically → zero endpoint changes.
+ - *Cost:* requires endpoints to return `IQueryable` (deferred) instead of `List` — a bigger contract change with an `IQueryable`-lifetime concern (connection alive until the framework materializes).
+ - Rejected for v1 (the per-endpoint attribute opt-in, still returning `List`, is lower-risk and gradual). Revisit for v2 paging, where the framework must own `skip`/`take`/`count` anyway.
+
+## Related
+
+- SIGDS topic (the DataSource this extends): [`AyCode.Services/docs/SIGNALR_DATASOURCE/README.md`](../../AyCode.Services/docs/SIGNALR_DATASOURCE/README.md) + its `_ISSUES` / `_TODO` companions.
+- Transport (SignalParams, hub dispatch, `AcWebSignalRHubBase`): [`AyCode.Services/docs/SIGNALR/README.md`](../../AyCode.Services/docs/SIGNALR/README.md) and [`AyCode.Services.Server/docs/SIGNALR/README.md`](../../AyCode.Services.Server/docs/SIGNALR/README.md).
+- Deprecation context (the path being replaced): `AcExpressionNode` in [`AyCode.Core/Serializers/Expressions/README.md`](../../AyCode.Core/Serializers/Expressions/README.md) + `AcSerializerCommon.ApplyQueryFromNode` / `ExecuteQueryFromNode`.
+- Cross-repo (client): AyCode.Blazor `MgGridBase` / `MgGridSignalRDataSource` (MGGRID topic) — no `docs/adr/` folder there yet; cross-ref to be added.
+- Related TODOs: to be filed (`ACCORE-SIGDS-T-*` + `ACBLAZOR-GRID-T-*`) — see Follow-ups.
+- v2 paging: a forthcoming ADR when server-side paging lands.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 8c6053e..6094823 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -17,7 +17,9 @@ See [`.github/skills/adr-author/SKILL.md`](../../.github/skills/adr-author/SKILL
| ID | Title | Status |
|---------------------------------------------------------|----------------------------------------------------------------------------------------------------|-----------------------|
| [0001](0001-user-bearer-token-flow.md) | User bearer-token authentication flow (HTTP + SignalR + tag dispatch) | Accepted (2026-04-25) |
-| [0003](0003-acbinary-streaming-receive-architecture.md) | AcBinary streaming receive — AsyncPipeReaderInput unified primitive and transport-agnostic helpers | Proposed (2026-04-27) |
+| [0002](0002-environment-infrastructure-to-framework.md) | Environment infrastructure lifted to the framework (BaseUrl, IsProdDb, env badge) — cross-ref to FruitBankHybridApp ADR 0002 | Accepted (2026-07-15) |
+| [0003](0003-acbinary-streaming-receive-architecture.md) | AcBinary streaming receive — AsyncPipeReaderInput unified primitive and transport-agnostic helpers | Accepted (2026-05-03) |
+| [0004](0004-signalr-datasource-server-side-filtering.md) | Server-side filtering for SignalR DataSource via DevExpress CriteriaOperator string + `[SignalRFilterText]` hub injection | Proposed (2026-07-15) |
## Related