@camstack/ui-library 1.2.192 → 1.2.193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,17 @@ export interface ClipsViewInput {
11
11
  * will stay `pending` for ever.
12
12
  */
13
13
  readonly plan: ClipBandPlan;
14
+ /**
15
+ * The provider the clip list was asked of, or `null` when the camera lists
16
+ * NO source to ask.
17
+ *
18
+ * Naming one is required (D554 amended), so "nobody to ask" is a real state and it
19
+ * has to be told apart from "not answered yet": the query never fires, its
20
+ * status stays `pending` for ever, and a surface that only consults the
21
+ * status spins against a question with no addressee. Exactly the trap
22
+ * `too-wide` has, arriving from the other end.
23
+ */
24
+ readonly provider: string | null;
14
25
  readonly clipsStatus: ClipQueryStatus;
15
26
  readonly clips: readonly Clip[];
16
27
  readonly sourcesStatus: ClipQueryStatus;
package/dist/index.cjs CHANGED
@@ -19593,11 +19593,18 @@ function resolveClipsView(input) {
19593
19593
  kind: "failed",
19594
19594
  message: input.sourcesError ?? "The source list could not be read"
19595
19595
  };
19596
- if (input.clipsStatus === "pending" || input.sourcesStatus === "pending") return { kind: "loading" };
19597
19596
  const notices = input.sources.map(describeSource);
19598
19597
  const answered = notices.filter((notice) => notice.answered);
19599
19598
  const silent = notices.filter((notice) => !notice.answered);
19600
19599
  const clips = input.clips;
19600
+ if (input.provider === null && input.sourcesStatus === "success") return {
19601
+ kind: "empty",
19602
+ band: input.plan.band,
19603
+ answered,
19604
+ silent,
19605
+ nobodyAnswered: true
19606
+ };
19607
+ if (input.clipsStatus === "pending" || input.sourcesStatus === "pending") return { kind: "loading" };
19601
19608
  if (clips.length === 0) return {
19602
19609
  kind: "empty",
19603
19610
  band: input.plan.band,
@@ -19689,23 +19696,34 @@ function resolveClipsView(input) {
19689
19696
  * ## Why the picker is a SELECT and not a multiselect
19690
19697
  *
19691
19698
  * The operator asked for *"un multiselect common"* while `listClips` still
19692
- * took `sources?: string[]`. It now takes ONE `provider` — *"il get videoclips
19693
- * prendere un provider opzionale, di default va su camstack … l'utilizzatore è
19694
- * uno solo"* — and a multi-pick control over a one-at-a-time argument offers a
19695
- * sum the hub cannot answer. So the control is the shared single-choice one,
19696
- * `Select`: the same component the Recordings page's own camera picker uses,
19697
- * which is what *"segue la nostra UI"* means here. Absent means the camera's
19698
- * BOUND provider, and the option that carries it is labelled with that
19699
- * provider's own sources rather than the word "default".
19699
+ * took `sources?: string[]`. It now takes ONE `provider` —
19700
+ * *"l'utilizzatore è uno solo"* — and a multi-pick control over a
19701
+ * one-at-a-time argument offers a sum the hub cannot answer. So the control is
19702
+ * the shared single-choice one, `Select`: the same component the Recordings
19703
+ * page's own camera picker uses, which is what *"segue la nostra UI"* means
19704
+ * here.
19705
+ *
19706
+ * ## The control's value is ALWAYS what the query carries
19707
+ *
19708
+ * There is no "default" option and no empty value. This section used to send
19709
+ * no `provider` at all until the operator chose, because the cap documented
19710
+ * absence as "the device's bound provider, which is CamStack on every camera".
19711
+ * No code implemented that: measured on device 592 (2026-09-20, bound to
19712
+ * `recorder` AND `provider-reolink`) a bare `listClips` with `limit: 3`
19713
+ * answered SIX rows — three per source, merged — while the default option read
19714
+ * "CamStack recordings". The label named one thing and the query asked two.
19715
+ *
19716
+ * Naming a provider is now required (D554 amended), and the default is this surface's
19717
+ * to make: the FIRST source the camera lists, shown selected in the control, so
19718
+ * what is on screen and what was asked are the same fact. A camera that lists
19719
+ * no source is asked NOTHING — the query never fires and the body says nobody
19720
+ * answered, rather than spinning against a question with no addressee.
19700
19721
  *
19701
19722
  * ## One provider is a fleet STATE
19702
19723
  *
19703
- * `listSources` derives from `deviceManager.getBindings`, and today nothing
19704
- * binds `provider-reolink` for `videoclips`, so a real camera answers with one
19705
- * provider. The selector is then inert and SAYS why. An empty or silently
19706
- * single-entry control would read as a defect; the binding store learning to
19707
- * hold several providers for a collection cap is what changes this, and
19708
- * nothing here needs to change with it.
19724
+ * `listSources` derives from `deviceManager.getBindings`. A camera bound to one
19725
+ * provider makes the selector inert, and it SAYS why: an empty or silently
19726
+ * single-entry control would read as a defect.
19709
19727
  */
19710
19728
  /** Newest-first, and bounded: a busy day on 592 lists 546 clips. */
19711
19729
  var CLIP_PAGE_LIMIT = 500;
@@ -19820,16 +19838,26 @@ function ClipsSection({ deviceId }) {
19820
19838
  }), [view, selected]);
19821
19839
  const plan = planClipBand(band);
19822
19840
  const sources = useVideoclipsListSources({ deviceId });
19841
+ const sourceRows = sources.data ?? [];
19842
+ /**
19843
+ * The provider the list is ASKED of: the operator's pick, else the first
19844
+ * source the camera lists. Empty only while the camera has no source at all
19845
+ * — and then nothing is asked, because there is nobody to ask.
19846
+ */
19847
+ const askedProvider = (0, react$1.useMemo)(() => {
19848
+ if (provider !== "") return provider;
19849
+ return sourceRows.find((row) => row.addonId !== void 0)?.addonId ?? "";
19850
+ }, [provider, sourceRows]);
19823
19851
  const clips = useVideoclipsListClips({
19824
19852
  deviceId,
19825
19853
  since: band.fromMs,
19826
19854
  until: band.toMs,
19827
19855
  limit: CLIP_PAGE_LIMIT,
19828
- ...provider === "" ? {} : { provider }
19829
- }, { enabled: plan.kind === "askable" });
19830
- const sourceRows = sources.data ?? [];
19856
+ provider: askedProvider
19857
+ }, { enabled: plan.kind === "askable" && askedProvider !== "" });
19831
19858
  const clipsView = resolveClipsView({
19832
19859
  plan,
19860
+ provider: askedProvider === "" ? null : askedProvider,
19833
19861
  clipsStatus: clips.status,
19834
19862
  clips: clips.data ?? [],
19835
19863
  sourcesStatus: sources.status,
@@ -19847,15 +19875,10 @@ function ClipsSection({ deviceId }) {
19847
19875
  if (labels === void 0) byAddon.set(addonId, [row.label]);
19848
19876
  else labels.push(row.label);
19849
19877
  }
19850
- const rest = [...byAddon].map(([value, labels]) => ({
19878
+ return [...byAddon].map(([value, labels]) => ({
19851
19879
  value,
19852
19880
  label: labels.join(" · ")
19853
19881
  }));
19854
- const first = rest[0];
19855
- return [{
19856
- value: "",
19857
- label: first === void 0 ? "The camera’s bound provider" : first.label
19858
- }, ...rest.slice(1)];
19859
19882
  }, [sourceRows]);
19860
19883
  const tableRows = (0, react$1.useMemo)(() => sourceRows.map((row) => ({
19861
19884
  notice: describeSource(row),
@@ -19933,7 +19956,7 @@ function ClipsSection({ deviceId }) {
19933
19956
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Select, {
19934
19957
  id: "clips-provider",
19935
19958
  options,
19936
- value: provider,
19959
+ value: askedProvider,
19937
19960
  disabled: options.length < 2,
19938
19961
  onChange: (e) => pick(e.target.value)
19939
19962
  })
package/dist/index.js CHANGED
@@ -19569,11 +19569,18 @@ function resolveClipsView(input) {
19569
19569
  kind: "failed",
19570
19570
  message: input.sourcesError ?? "The source list could not be read"
19571
19571
  };
19572
- if (input.clipsStatus === "pending" || input.sourcesStatus === "pending") return { kind: "loading" };
19573
19572
  const notices = input.sources.map(describeSource);
19574
19573
  const answered = notices.filter((notice) => notice.answered);
19575
19574
  const silent = notices.filter((notice) => !notice.answered);
19576
19575
  const clips = input.clips;
19576
+ if (input.provider === null && input.sourcesStatus === "success") return {
19577
+ kind: "empty",
19578
+ band: input.plan.band,
19579
+ answered,
19580
+ silent,
19581
+ nobodyAnswered: true
19582
+ };
19583
+ if (input.clipsStatus === "pending" || input.sourcesStatus === "pending") return { kind: "loading" };
19577
19584
  if (clips.length === 0) return {
19578
19585
  kind: "empty",
19579
19586
  band: input.plan.band,
@@ -19665,23 +19672,34 @@ function resolveClipsView(input) {
19665
19672
  * ## Why the picker is a SELECT and not a multiselect
19666
19673
  *
19667
19674
  * The operator asked for *"un multiselect common"* while `listClips` still
19668
- * took `sources?: string[]`. It now takes ONE `provider` — *"il get videoclips
19669
- * prendere un provider opzionale, di default va su camstack … l'utilizzatore è
19670
- * uno solo"* — and a multi-pick control over a one-at-a-time argument offers a
19671
- * sum the hub cannot answer. So the control is the shared single-choice one,
19672
- * `Select`: the same component the Recordings page's own camera picker uses,
19673
- * which is what *"segue la nostra UI"* means here. Absent means the camera's
19674
- * BOUND provider, and the option that carries it is labelled with that
19675
- * provider's own sources rather than the word "default".
19675
+ * took `sources?: string[]`. It now takes ONE `provider` —
19676
+ * *"l'utilizzatore è uno solo"* — and a multi-pick control over a
19677
+ * one-at-a-time argument offers a sum the hub cannot answer. So the control is
19678
+ * the shared single-choice one, `Select`: the same component the Recordings
19679
+ * page's own camera picker uses, which is what *"segue la nostra UI"* means
19680
+ * here.
19681
+ *
19682
+ * ## The control's value is ALWAYS what the query carries
19683
+ *
19684
+ * There is no "default" option and no empty value. This section used to send
19685
+ * no `provider` at all until the operator chose, because the cap documented
19686
+ * absence as "the device's bound provider, which is CamStack on every camera".
19687
+ * No code implemented that: measured on device 592 (2026-09-20, bound to
19688
+ * `recorder` AND `provider-reolink`) a bare `listClips` with `limit: 3`
19689
+ * answered SIX rows — three per source, merged — while the default option read
19690
+ * "CamStack recordings". The label named one thing and the query asked two.
19691
+ *
19692
+ * Naming a provider is now required (D554 amended), and the default is this surface's
19693
+ * to make: the FIRST source the camera lists, shown selected in the control, so
19694
+ * what is on screen and what was asked are the same fact. A camera that lists
19695
+ * no source is asked NOTHING — the query never fires and the body says nobody
19696
+ * answered, rather than spinning against a question with no addressee.
19676
19697
  *
19677
19698
  * ## One provider is a fleet STATE
19678
19699
  *
19679
- * `listSources` derives from `deviceManager.getBindings`, and today nothing
19680
- * binds `provider-reolink` for `videoclips`, so a real camera answers with one
19681
- * provider. The selector is then inert and SAYS why. An empty or silently
19682
- * single-entry control would read as a defect; the binding store learning to
19683
- * hold several providers for a collection cap is what changes this, and
19684
- * nothing here needs to change with it.
19700
+ * `listSources` derives from `deviceManager.getBindings`. A camera bound to one
19701
+ * provider makes the selector inert, and it SAYS why: an empty or silently
19702
+ * single-entry control would read as a defect.
19685
19703
  */
19686
19704
  /** Newest-first, and bounded: a busy day on 592 lists 546 clips. */
19687
19705
  var CLIP_PAGE_LIMIT = 500;
@@ -19796,16 +19814,26 @@ function ClipsSection({ deviceId }) {
19796
19814
  }), [view, selected]);
19797
19815
  const plan = planClipBand(band);
19798
19816
  const sources = useVideoclipsListSources({ deviceId });
19817
+ const sourceRows = sources.data ?? [];
19818
+ /**
19819
+ * The provider the list is ASKED of: the operator's pick, else the first
19820
+ * source the camera lists. Empty only while the camera has no source at all
19821
+ * — and then nothing is asked, because there is nobody to ask.
19822
+ */
19823
+ const askedProvider = useMemo(() => {
19824
+ if (provider !== "") return provider;
19825
+ return sourceRows.find((row) => row.addonId !== void 0)?.addonId ?? "";
19826
+ }, [provider, sourceRows]);
19799
19827
  const clips = useVideoclipsListClips({
19800
19828
  deviceId,
19801
19829
  since: band.fromMs,
19802
19830
  until: band.toMs,
19803
19831
  limit: CLIP_PAGE_LIMIT,
19804
- ...provider === "" ? {} : { provider }
19805
- }, { enabled: plan.kind === "askable" });
19806
- const sourceRows = sources.data ?? [];
19832
+ provider: askedProvider
19833
+ }, { enabled: plan.kind === "askable" && askedProvider !== "" });
19807
19834
  const clipsView = resolveClipsView({
19808
19835
  plan,
19836
+ provider: askedProvider === "" ? null : askedProvider,
19809
19837
  clipsStatus: clips.status,
19810
19838
  clips: clips.data ?? [],
19811
19839
  sourcesStatus: sources.status,
@@ -19823,15 +19851,10 @@ function ClipsSection({ deviceId }) {
19823
19851
  if (labels === void 0) byAddon.set(addonId, [row.label]);
19824
19852
  else labels.push(row.label);
19825
19853
  }
19826
- const rest = [...byAddon].map(([value, labels]) => ({
19854
+ return [...byAddon].map(([value, labels]) => ({
19827
19855
  value,
19828
19856
  label: labels.join(" · ")
19829
19857
  }));
19830
- const first = rest[0];
19831
- return [{
19832
- value: "",
19833
- label: first === void 0 ? "The camera’s bound provider" : first.label
19834
- }, ...rest.slice(1)];
19835
19858
  }, [sourceRows]);
19836
19859
  const tableRows = useMemo(() => sourceRows.map((row) => ({
19837
19860
  notice: describeSource(row),
@@ -19909,7 +19932,7 @@ function ClipsSection({ deviceId }) {
19909
19932
  children: /* @__PURE__ */ jsx(Select, {
19910
19933
  id: "clips-provider",
19911
19934
  options,
19912
- value: provider,
19935
+ value: askedProvider,
19913
19936
  disabled: options.length < 2,
19914
19937
  onChange: (e) => pick(e.target.value)
19915
19938
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/ui-library",
3
- "version": "1.2.192",
3
+ "version": "1.2.193",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",