fimo 2.4.1 → 2.5.0-staging.2

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.
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import { getFimoSource } from 'fimo/astro';
3
4
 
4
5
  interface Props {
@@ -11,4 +12,4 @@ const { value, as: Component = 'span', ...props } = Astro.props;
11
12
  const source = getFimoSource(value);
12
13
  ---
13
14
 
14
- {value != null && <Component {...props} data-fimo-source={source}>{String(value.valueOf())}</Component>}
15
+ {value != null && <Component {...withoutFimoEditingMetadata(props)} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}>{String(value.valueOf())}</Component>}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import { getFimoSource } from 'fimo/astro';
3
4
 
4
5
  interface Props {
@@ -20,4 +21,4 @@ const content = validDate?.toLocaleDateString(
20
21
  const source = getFimoSource(value);
21
22
  ---
22
23
 
23
- {validDate && <time {...props} datetime={dateTime} data-fimo-source={source}>{content}</time>}
24
+ {validDate && <time {...withoutFimoEditingMetadata(props)} datetime={dateTime} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}>{content}</time>}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import { getFimoSource } from 'fimo/astro';
3
4
 
4
5
  interface Props {
@@ -19,4 +20,4 @@ const content = validDate?.toLocaleString(locale, options ?? { dateStyle: 'mediu
19
20
  const source = getFimoSource(value);
20
21
  ---
21
22
 
22
- {validDate && <time {...props} datetime={dateTime} data-fimo-source={source}>{content}</time>}
23
+ {validDate && <time {...withoutFimoEditingMetadata(props)} datetime={dateTime} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}>{content}</time>}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import type { FimoMedia } from 'fimo/astro';
3
4
  import { getFimoSource } from 'fimo/astro';
4
5
 
@@ -65,7 +66,7 @@ const source = getFimoSource(value);
65
66
  {
66
67
  image && (
67
68
  <img
68
- {...props}
69
+ {...withoutFimoEditingMetadata(props)}
69
70
  src={image.src}
70
71
  alt={image.alt}
71
72
  width={image.width}
@@ -73,7 +74,7 @@ const source = getFimoSource(value);
73
74
  sizes={image.sizes}
74
75
  srcset={image.srcSet}
75
76
  style={imageStyle}
76
- data-fimo-source={source}
77
+ data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}
77
78
  />
78
79
  )
79
80
  }
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import { getFimoSource } from 'fimo/astro';
3
4
 
4
5
  interface Props {
@@ -11,4 +12,4 @@ const { value, as: Component = 'span', ...props } = Astro.props;
11
12
  const source = getFimoSource(value);
12
13
  ---
13
14
 
14
- {value != null && <Component {...props} data-fimo-source={source}>{JSON.stringify(value)}</Component>}
15
+ {value != null && <Component {...withoutFimoEditingMetadata(props)} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}>{JSON.stringify(value)}</Component>}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import type { FimoRichText } from 'fimo/astro';
3
4
  import { getFimoSource } from 'fimo/astro';
4
5
 
@@ -14,4 +15,4 @@ const source = getFimoSource(value);
14
15
  const html = renderRichTextHtml(value?.content);
15
16
  ---
16
17
 
17
- {value && <div {...props} data-fimo-source={source} data-fimo-richtext="" set:html={html} />}
18
+ {value && <div {...withoutFimoEditingMetadata(props)} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined} data-fimo-richtext={isFimoEditingMetadataEnabled() ? "" : undefined} set:html={html} />}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import {
3
4
  mergeAstroImageStyle,
4
5
  resolveImagePresentation,
@@ -55,7 +56,7 @@ const imageStyle = mergeAstroImageStyle(style, image.objectFit);
55
56
  ---
56
57
 
57
58
  <img
58
- {...props}
59
+ {...withoutFimoEditingMetadata(props)}
59
60
  src={image.src}
60
61
  alt={image.alt}
61
62
  width={image.width}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import { getFimoParts, getFimoSource } from 'fimo/astro';
3
4
 
4
5
  interface Props {
@@ -12,11 +13,11 @@ const parts = getFimoParts(value);
12
13
  const source = getFimoSource(value);
13
14
  ---
14
15
 
15
- <Component {...props} data-fimo-source={source}>
16
+ <Component {...withoutFimoEditingMetadata(props)} data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}>
16
17
  {
17
18
  parts && parts.length > 1
18
19
  ? parts.map((part) =>
19
- part.source ? <span data-fimo-source={part.source}>{part.text}</span> : part.text
20
+ part.source ? <span data-fimo-source={isFimoEditingMetadataEnabled() ? part.source : undefined}>{part.text}</span> : part.text
20
21
  )
21
22
  : String(value ?? '')
22
23
  }
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { isFimoEditingMetadataEnabled, withoutFimoEditingMetadata } from 'fimo/preview';
2
3
  import type { FimoMedia } from 'fimo/astro';
3
4
  import { getFimoSource } from 'fimo/astro';
4
5
 
@@ -33,7 +34,7 @@ const shouldPlayInline = playsinline ?? playsInline ?? true;
33
34
  {
34
35
  value && (
35
36
  <video
36
- {...props}
37
+ {...withoutFimoEditingMetadata(props)}
37
38
  src={overrideSrc ?? value.url}
38
39
  width={value.width}
39
40
  height={value.height}
@@ -41,7 +42,7 @@ const shouldPlayInline = playsinline ?? playsInline ?? true;
41
42
  loop={loop}
42
43
  muted={muted}
43
44
  playsinline={shouldPlayInline}
44
- data-fimo-source={source}
45
+ data-fimo-source={isFimoEditingMetadataEnabled() ? source : undefined}
45
46
  />
46
47
  )
47
48
  }
@@ -4,11 +4,24 @@ Pageview aggregates for the **published** site are stored in Fimo's analytics pi
4
4
 
5
5
  Analytics has a **retained preview for every org** — free included. The free default is a 7-day retention window, so short ranges (`today`, `yesterday`, `this_week`, `last_week`, `last_7_days`) work for everyone. **Extended date ranges and longer retention are paid** — `last_30_days`, `last_90_days`, `this_month`, `last_month`, `last_quarter`, `last_year`, and `custom` require the `canUseExtendedDateRanges` privilege, which only paid orgs have. Asking for an extended range as a free user returns a 403 with an upgrade hint.
6
6
 
7
+ ## Bot traffic is excluded
8
+
9
+ Every number below is **humans only**. Bot hits — AI crawlers and headless browsers — are tagged
10
+ at ingest and left out of visitors, pageviews, bounce rate, visit duration, the series and every
11
+ breakdown. This is not a filter the caller opts into; it is what the numbers mean.
12
+
13
+ The response says what it dropped, so you can answer "am I being crawled" without a second call:
14
+ `botPageviews` and `botVisitors` always describe the whole window.
15
+
16
+ `--device bot` goes and looks at that traffic. It fills `topBots`, which names the crawler
17
+ (GPTBot, ClaudeBot, Googlebot, …). Expect a bounce rate near 100% and a visit duration of 0 —
18
+ that is what a crawler looks like, not a bug.
19
+
7
20
  ## CLI commands
8
21
 
9
22
  ```bash
10
- fimo analytics pageviews [--preset <p>] [--days 7|30|90] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--group-by day|week|month|quarter]
11
- fimo analytics top <dimension> [--limit N] [--preset|--days|--from/--to]
23
+ fimo analytics pageviews [--preset <p>] [--days 7|30|90] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--group-by day|week|month|quarter] [--device bot|desktop|mobile|tablet]
24
+ fimo analytics top <dimension> [--limit N] [--preset|--days|--from/--to] [--device ...]
12
25
  fimo analytics annotations list [--preset|--days|--from/--to]
13
26
  fimo analytics annotations set <YYYY-MM-DD> "<content>"
14
27
  fimo analytics annotations delete <YYYY-MM-DD>
@@ -25,17 +38,30 @@ The headline command returns everything in one payload — KPIs, the visitors se
25
38
  ```json
26
39
  {
27
40
  "range": { "preset": "last_30_days", "effectiveFrom": "...", "effectiveTo": "...", "groupBy": "day", ... },
41
+ "deviceFilter": null,
28
42
  "visitors": 1240, "pageviews": 3870, "bounceRate": 42.5, "viewsPerVisit": 3.12, "averageVisitDurationSeconds": 95.4,
43
+ "botPageviews": 18940, "botVisitors": 18902,
29
44
  "visitorsSeries": [{ "date": "2026-04-26", "visitors": 38 }, ...],
30
45
  "topPages": [{ "path": "/", "pageName": "Home", "visitors": 612, "pageviews": 980 }, ...],
31
46
  "topBrowsers": [{ "key": "Chrome", "visitors": 720, "pageviews": 2110 }, ...],
32
47
  "topDevices": [...],
48
+ "topBots": [],
49
+ "topAiAssistants": [{ "key": "ChatGPT", "visitors": 118, "pageviews": 442 }, ...],
33
50
  "topCountries": [...],
34
51
  "topSources": [...],
35
52
  "annotations": [{ "date": "2026-05-20", "content": "Launched", "updatedAt": "...", "updatedByUserId": "..." }]
36
53
  }
37
54
  ```
38
55
 
56
+ `topBots` is empty unless `--device bot` is set — it belongs to that view.
57
+
58
+ `topAiAssistants` is who sent you readers: a person who follows a citation inside ChatGPT,
59
+ Perplexity or Claude arrives with that assistant as the referrer. Two honest limits worth
60
+ repeating to a user: arrivals from an assistant's desktop or phone app usually carry no referrer
61
+ and land in `Direct`, so the number is a floor; and being cited without being clicked is
62
+ invisible. No assistant passes the user's question, so "which assistants send readers" is
63
+ answerable and "what they asked" is not.
64
+
39
65
  `fimo analytics top <dimension>` is a convenience wrapper — it makes the same call and returns just the corresponding `top*` array, optionally truncated by `--limit`.
40
66
 
41
67
  `fimo analytics annotations list` similarly filters to just the `annotations` array.
@@ -58,5 +84,9 @@ A natural usage pattern: after `fimo deploy --publish` for a notable change, set
58
84
  - "Show me my analytics" / "how is my site performing" / "traffic since last week" → `fimo analytics pageviews --days 7`.
59
85
  - "What countries are my visitors from?" → `fimo analytics top countries`.
60
86
  - "Mark today as launch day on the chart" → `fimo analytics annotations set <today> "Launch"`.
87
+ - "Am I being crawled / is AI scraping my site?" → `fimo analytics pageviews --device bot --days 30`.
88
+ - "Is ChatGPT sending me traffic?" → `fimo analytics pageviews --days 30`, then read `topAiAssistants`.
89
+ - "My visitor count dropped" → check whether they are comparing against a pre-exclusion number;
90
+ `botPageviews` says how much of the old figure was never a person.
61
91
 
62
92
  For free-tier users who hit the extended-range gate, either stick to `last_7_days` / `last_week` / shorter, or link them to upgrade for full retention and extended ranges.
@@ -79,6 +79,8 @@ Changes save automatically as you fill in each field (the save is debounced), so
79
79
 
80
80
  Your edits save automatically as you change each field (the save is debounced), so there is no Save button. Watch the top bar for the save status (Saving / Saved). The entry editor header lets you move to the previous or next entry, delete the current entry, or copy its link.
81
81
 
82
+ For a saved localized Collection entry, use the language selector in the entry header to change that entry's language without closing it. The Collection list keeps its own language and rows. If the entry has no translation in the chosen language, the editor stays open and reports that no entry was found. Singles and new entries continue to use the route language because they do not have a saved entry variant to keep open.
83
+
82
84
  Press **Alt+J** to move to the next entry or **Alt+K** to move to the previous one (**Option+J** and **Option+K** on macOS). These shortcuts also work while your cursor is inside a rich-text field, and pending changes save before Fimo moves.
83
85
 
84
86
  If a field is invalid, the editor keeps the change unsaved and pins a status bar below the header. Choose **Review issues** to jump to the first invalid field, then **Next issue** to move through the remaining fields. Collapsed blocks open automatically. Once every issue is fixed, the editor confirms that all fields are valid and resumes saving.
@@ -1,3 +1,4 @@
1
+ import { resolveFimoEditingMetadata } from '../../../runtime/preview/editing-metadata.js';
1
2
  import { findStaleFimoOverride, resolveLocalRuntimeEnv, staleOverrideMessage, } from '../../../runtime/shared/local-runtime-env.js';
2
3
  /**
3
4
  * Point the app at the Fimo env of the current git branch.
@@ -19,6 +20,8 @@ import { findStaleFimoOverride, resolveLocalRuntimeEnv, staleOverrideMessage, }
19
20
  * authoritative.
20
21
  */
21
22
  export default function runtimeEnvPlugin() {
23
+ // External SSR dependencies bypass Vite's define replacements.
24
+ const ssr = { noExternal: ['fimo', 'fimo-react', 'fimo-sveltekit'] };
22
25
  return {
23
26
  name: 'fimo:runtime-env',
24
27
  enforce: 'pre',
@@ -35,15 +38,20 @@ export default function runtimeEnvPlugin() {
35
38
  async handler(config, { command, mode }) {
36
39
  const cwd = config.root ?? process.cwd();
37
40
  const derived = resolveLocalRuntimeEnv({ cwd });
38
- if (!derived) {
39
- return;
40
- }
41
41
  // Keep the public neutral entry importable without installing Vite.
42
42
  // The consuming framework always provides Vite when it invokes the
43
43
  // plugin, but `import('fimo/vite/runtime-env')` is also a valid package
44
44
  // boundary probe for projects that only install the core runtime.
45
45
  const { loadEnv } = await import('vite');
46
46
  const explicit = { ...loadEnv(mode, config.envDir ?? cwd, ''), ...process.env };
47
+ const define = {
48
+ 'process.env.FIMO_EDITING_METADATA': JSON.stringify(String(resolveFimoEditingMetadata(command === 'serve' && mode !== 'production' && explicit.NODE_ENV !== 'production'
49
+ ? 'development'
50
+ : 'production', explicit.FIMO_EDITING_METADATA))),
51
+ };
52
+ if (!derived) {
53
+ return { define, ssr };
54
+ }
47
55
  if (command === 'serve') {
48
56
  const stale = findStaleFimoOverride(explicit, derived);
49
57
  if (stale) {
@@ -68,8 +76,8 @@ export default function runtimeEnvPlugin() {
68
76
  // is how Astro's server runtime reads `FIMO_CONTENT_API_URL`. A curated
69
77
  // list silently misses whichever framework reads a key that is not on it.
70
78
  // Every value here is a public tenant URL or an env name; none is secret.
71
- const define = Object.fromEntries(missing.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)]));
72
- return Object.keys(define).length > 0 ? { define } : undefined;
79
+ Object.assign(define, Object.fromEntries(missing.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)])));
80
+ return { define, ssr };
73
81
  },
74
82
  },
75
83
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-09-08T08:30:04.792Z",
4
+ "bundledAt": "2026-09-08T11:12:03.529Z",
5
5
  "cliVersion": "2.4.1",
6
6
  "external": [
7
7
  "oxc-parser",