43 Commits

Author SHA1 Message Date
Olli Paloviita 0f211bea64 editor: apply deferred (pending) codegen edits on connect
Accept codegen requests queued while no machine was connected: the poll
loop applies them like any other edit and logs "queued by <name>"
attribution. Update the editor docs to describe pending edits and code
sync.
2026-07-13 02:18:41 +03:00
Olli Paloviita 5730086240 editor: auto-resolve duplicate editIds via static analysis
A slug used at two distinct call sites silently merged both actions into
one runtime identity (the second became a phantom loop-repeat, its edits
unreachable) and was only caught reactively as an ambiguous-edit-id.

Static analysis now finds every duplicate up front, keeps the first
occurrence, and re-stamps the rest with fresh, never-reused slugs. Wired
into record (before the record run), the screenci edit startup handshake,
and the interactive codegen apply (retry-after-resolve, still-ambiguous
still throws). Genuine loops (one call site) stay untouched.

- codemod: collectEditIdOccurrences, renameEditIdAtCall
- editIdStamp: planDuplicateEditIdFixes (skips in-use slugs)
- record path reuses one --list discovery pass for both title and
  duplicate-editId checks
2026-07-13 01:54:23 +03:00
Olli Paloviita 9f3f1beebf editor: split a code autoZoom into two via codegen
blockRemoveEdit now unwraps autoZoom blocks (not only hide/speed/time), and
codeSync orders the unwrap before the two re-wraps in one pass, so splitting a
code-authored autoZoom yields two sibling brackets carrying the original zoom
options. Adds codeSync.spec coverage and documents the flow in editor.md.
2026-07-12 22:15:50 +03:00
Olli Paloviita 713f351bfc overlays: drop the language-major form (shared across languages)
Overlays no longer vary by language. video.overlays(...) accepts only a
names-only array or a shared content-major object; the language-major
spelling is rejected at the type level and throws a clear ScreenciError at
declare time. Narration (and the hidden values/audio) keep the per-language
form.

Removes the runtime per-language overlay resolution (buildOverlays is now
shared-only), the overlay language contribution/warning paths, the
AssetTranslation type and asset translations fields, and the assetStart branch
of the translation fold. Docs updated.
2026-07-12 21:49:49 +03:00
Olli Paloviita 3349cf73b9 editor: drag-to-resize adjacent waitForTimeout, merge back-to-back sleeps
Make recorded waitForTimeout calls web-editable and keep the source in sync
with timeline drags:

- Unlock recorded numeric waitForTimeout delays (instrument) so their
  durationMs can be rewritten in code instead of being locked.
- Add a durationMs codegen path (codeSync): locate the wait via a stamped
  neighbor action (findRecordedWait) and rewrite the numeric arg in place, or
  remove the call at 0. Refuse safely when the source has drifted.
- Coalesce back-to-back waitForTimeout statements into one summed call
  (mergeAdjacentWaitEdits), run as a post-pass on every file a sync changed.
  Adjacency is judged per source block, so it never crosses control flow;
  comment-separated sleeps are preserved.
- Carry schemaKind on editable snapshot entries so codegen can tell a recorded
  delay apart from other unstamped entries.

Docs and unit tests included.
2026-07-12 21:29:10 +03:00
Olli Paloviita 26b1d3f330 blocks: move identity to an editId option, stamp it on edit start
BREAKING: the name-first overloads of hide/speed/time are removed. A block's
stable identity now lives in the trailing options object, like an action's:
hide(fn, { editId: 'setup' }). The dev-startup handshake that stamps action
editIds now stamps blocks too (the options object lands right after the
callback argument), so every block gains a stable identity when an edit
session starts. blockRemoveEdit resolves blocks by the editId option (legacy
string-name args still match), making every stamped block web-removable.
2026-07-12 19:01:15 +03:00
Olli Paloviita 9965beb7ae Merge remote-tracking branch 'origin/editor' into editor
# Conflicts:
#	src/applyCodegen.spec.ts
2026-07-12 17:05:21 +03:00
Olli Paloviita 6d875ef0f0 Merge branch 'fable6' of /home/olli/projects/screenci-mono/worktrees/fable6/packages/screenci into editor 2026-07-12 16:40:05 +03:00
Olli Paloviita 0ad2115cf9 editor splits: add fromLeadMs/untilLeadMs for cuts before the first interaction
A gap span could only anchor forward (a sleep after an interaction's end or
a delay into it), so a cut over the footage leading into the first
interaction was unrepresentable. fromLeadMs pulls the span start to before
its anchor (codegen emits it as a leading waitForTimeout inside the wrap);
untilLeadMs is its end-boundary twin for editor-only bare splits. Bare-split
detection treats matching leads like matching sleeps.
2026-07-12 16:31:35 +03:00
Olli Paloviita c2aa007be3 Remove editor-owned overrides; codegen editor edits into code
Editor edits no longer fetch backend-stored overrides at record time.
Instead every edit is codegenned into the user's .screenci.ts sources
over the dev channel (or fails when no machine is connected).

- Remove the CLI override fetchers and their env plumbing
  (SCREENCI_VALUES_OVERRIDES / _RECORD_OPTIONS / _WEB_LANGUAGES), the
  parsers/merge in runtimeMode, resolveEffectiveRecordOptions, and the
  SCREENCI_DEBUG_OVERRIDES debug dump.
- Reconcile the languages model: drop the 'studio' sentinel, studioSeed,
  studioOwned, pending, and web-language unioning. A video with no
  .languages() is the implicit 'en' default; an explicit .languages([...])
  set is unioned with per-feature keys and filtered by --languages.
- Add codegen edit kinds and codemods: languagesEdit (setVideoLanguages),
  valuesEdit (setValuesValue), and editorMediaEdit (setEditorMedia).
- Add the { editor: '<name>' } media declaration for backend-hosted,
  editor-uploaded overlays / narration audio / audio tracks: the bytes
  stay in the backend, but the item is explicit in code (no local path).
- Rename internal resolveStudio* helpers to resolve*Base; keep wire/DB
  keys that deployed CLIs and stored recordings depend on.
- Update docs (languages, editor, overlays, narration, video-script-basics).
2026-07-12 16:29:41 +03:00
Olli Paloviita 13b6a36b48 codegen: format edited files with the project's Prettier, enabled by init
Editor codegen edits and dev startup editId stamping now format each
changed file with the user project's own prettier install before
writing. Formatting is gated on prettier resolving and a Prettier
config file being found for the edited file, so it is opt-in by
presence and never fails the edit: errors pass the content through
unchanged with a warning.

screenci init enables it by scaffolding a minimal user-editable
.prettierrc (2-space indent, single quotes, no semicolons, matching
the generated example style) and installing prettier in the island,
plus a format script and README note. Docs updated (editor.md,
manual-setup.mdx).
2026-07-12 16:02:03 +03:00
Olli Paloviita e30d12b1b3 docs: finish codegen-model pass on editor and cli docs
Remove the stale screenci status and sync sections from the CLI reference
(both commands are gone on this branch, codegen writes edits into sources
directly) and reword the timeline editing intros that still claimed edits
apply without code changes.
2026-07-12 13:32:15 +03:00
Olli Paloviita 36223a54b5 Merge fable4: align docs with the preview-first editor and screenci dev editing
# Conflicts:
#	docs/cli.mdx
#	docs/editor.md
2026-07-12 13:26:50 +03:00
Olli Paloviita c160b8a2f2 docs: align docs with the preview-first editor and screenci dev editing
Rewrite editor.md around the sync-gated codegen model (connected machine,
live preview, timeline, Export-first), fix the record vs export flow in
overview, agent-integration, manual-setup, cli, ci-setup, version-history,
public-urls-and-embeds, update-screenci, and video-script-basics, fix the
broken captureAudio examples (boolean | { gain }), document accent and the
web narration/overlay editing, add enableCaptureAudio to configuration, and
rename the CLI's Studio strings to Editor.
2026-07-12 10:28:39 +03:00
Olli Paloviita 075396f935 codegen: typed refusal reasons, aliased-import resolution, stale wrap cleanup, shared wire types
- planCodeSync unappliable items now carry a typed UnappliableReason
  plus the human message; applyCodegenRequest errors name the reason so
  the editor toast is actionable.
- Block/point/effect calls resolve through the file's import table, so
  aliased imports (autoZoom as az) are found, wrapped, unwrapped, and
  reused on insert; unresolvable functions refuse with
  unresolved-import.
- plannedRemoval unwraps stale editor-owned zoom/hide/speed/time wraps
  (anonymous blocks over the anchors) instead of leaving them in code.
- timelineEdits gains paramEditIdFor/renameEditIdFor and parseEditId,
  exported via the new screenci/timeline-edits subpath so the web app
  compiles against the same wire types and id encoding.
- Tests: refusal reasons, alias resolution, stale wrap unwrapping,
  TS-absent guard, plain-JS parsing, locator alias chains, editsOverlap.
- Docs: refusal reasons and alias support in cli.mdx and editor.md.
2026-07-12 03:22:37 +03:00
Olli Paloviita 55b3fb0993 Merge editor-branch screenci (codegen model) into fable6
Adapts the scissors additions to the codegen architecture: blockRemoveEdit
is routed as a codify record through splitTimelineEditsByVideo and applied
by the dev codegen channel; bare-split hides stay editor-only (skipped by
the planner); the sync-era parse/apply/report subsystem removed on the
editor branch stays removed.
2026-07-12 00:22:21 +03:00
Olli Paloviita eacc5df17a editor codegen for options and narration, dev source watching
- optionsEdit and narrationEdit records (timeline edits v4): render/record
  option snapshots merge into .renderOptions/.recordOptions builder calls
  (section added when missing), narration cue values merge into the
  video.narration declaration via the new setNarrationValue codemod
  (content-major and language-major forms, conversion to language-major on
  a non-default language edit, names-only declarations stay app-managed)
- applyCodegenRequest wires the new records into planCodeSync
- screenci dev watches the managed videos' test sources and
  screenci.config.ts (src/devWatch.ts): a real content change re-records
  the affected videos as previews through a new machine-local record job
  in the listen loop; codegen self-writes are baseline-suppressed;
  --no-watch disables it
- docs: cli.mdx dev watching, editor.md option/narration codegen coverage
2026-07-12 00:04:38 +03:00
Olli Paloviita 10276a703b editor: hidden-action markers, bare-split skip, and named-block unwrap for the web scissors feature
- Record a hiddenAction marker event for every instrumented action inside
  hide(), so the web editor can show what a hide suppresses.
- Skip zero-width hide gapSpanEdits at sync: bare split markers stay
  editor-only state and are never codified as empty hide() blocks.
- New blockRemoveEdit record + unwrapBlockCall codemod: unwrap a NAMED
  hide/speed/time block from source, keeping the wrapped calls and their
  waitForTimeout pacing.
2026-07-12 00:03:13 +03:00
Olli Paloviita 4eb5805499 Merge branch 'fable4' into editor
# Conflicts:
#	docs/cli.mdx
2026-07-11 22:13:56 +03:00
Olli Paloviita 16dcce8e18 Merge code-as-source-of-truth editing (fable3)
Editor edits now arrive over the dev channel as codegen requests and are
written straight into the .screenci.ts sources; recordings always run purely
from code values.

- Keep data.json across uploads (media-only cleanup, last-data.json rename)
  and stamp a sourceHash of the test file into its metadata.
- screenci dev startup handshake: skip recording when the source hash matches
  and every editable action has an editId; otherwise stamp missing editIds
  and re-record as a preview. New --grep, --force-record and
  --record-kill-window flags.
- Dev loop: killable background record slot, serial codegen applies acked
  over /cli/dev/report-codegen, fast-poll during active editing, kill-or-queue
  policy for superseding triggers, sync-state timeline locking.
- Removed: editable-actions.json and action-params.json snapshots,
  SCREENCI_TIMELINE_EDITS injection, runtime override application,
  screenci sync / status / reset-web-edits and dev --sync.
2026-07-11 22:02:20 +03:00
Olli Paloviita f34dfb5edb release: hide unfinished features from the SDK surface and docs
Comment out (not delete) the screenshot(), narration positioning
(moveNarration/resizeNarration), setBackground, background audio, and
selected() dependency exports; remove values()/audio() from the public
builder type while keeping them attached at runtime. Move the values,
audio, screenshots, and dependencies guides out of the docs manifest
(sources now live in docs/removed/ at the mono repo root), trim the
overlay-updates guide to the still-exported APIs, and scrub mentions
from the remaining docs and the agent skill.
2026-07-11 21:52:56 +03:00
Olli Paloviita 04393fa30e feat: code-as-source-of-truth editing via dev codegen channel
Editor edits now arrive over the dev channel as codegen requests and are
written straight into the .screenci.ts sources; recordings always run purely
from code values.

- Keep data.json across uploads (media-only cleanup, last-data.json rename)
  and stamp a sourceHash of the test file into its metadata.
- screenci dev startup handshake: skip recording when the source hash matches
  and every editable action has an editId; otherwise stamp missing editIds
  and re-record as a preview. New --grep, --force-record and
  --record-kill-window flags.
- Dev loop: records run in a killable background slot while the loop keeps
  polling; codegen requests apply serially via the codemod pipeline and are
  acked over /cli/dev/report-codegen; fast-poll during active editing;
  kill-or-queue policy for superseding record triggers; sync-state reporting
  locks editor timelines during the startup handshake.
- Remove the server-override machinery: editable-actions.json and
  action-params.json snapshots, SCREENCI_TIMELINE_EDITS injection, runtime
  override application, screenci sync / status / reset-web-edits and
  dev --sync.
2026-07-11 21:52:29 +03:00
Olli Paloviita 8e0929d707 cli: rename --publish to --export (deprecated aliases kept), docs talk about exports 2026-07-11 20:29:20 +03:00
Olli Paloviita d3fb8d80bf editor: web-editable cursor curves with visual bezier editing support
- cursorCurve: controlPointsToTuple (inverse mapping) and parseCursorCurve
- instrument: stamp effective moveCurve/moveCurviness into editable defaults
  at every pointer wrapper, and apply saved curve overrides at record time
- codeSync: codify cursor-move param edits (move.duration/speed/easing/
  curve/curviness/delayAfter, duration, easing, dragSteps) onto the stamped
  call, with duration-vs-speed exclusivity and an overlap guard
- easing: browser-safe EASING_NAMES re-export for the web editor
- docs: visual curve editing and cursor-move codesync
2026-07-11 09:38:06 +03:00
Olli Paloviita dba07921f3 docs: rewrite to the linear call-position model
Drop timestamp()/waitSince()/place* and the sync-prompt agent path from
the guides; describe effects as call-position block wrappers (autoZoom/
hide/speed/time) plus waitForTimeout gaps, keyed to editId. Move the last
config-level use.recordOptions/renderOptions examples to the per-video
video.recordOptions()/renderOptions() builder form.
2026-07-09 00:57:42 +03:00
Olli Paloviita e0ac78f905 sync: apply edits exclusively by editId, remove heuristic matching
Option edits resolve their call sites via the editId recorded on
action-param records; sleepBefore, autoZoom offsets, and placed events
anchor by slug too. The selector/occurrence lexical matcher, line
anchors, and loop detection are gone: unstamped actions are never
guessed at and route to the agent prompt until a record plus sync
stamps them.
2026-07-08 21:21:05 +03:00
Olli Paloviita 8310f5b3e4 editId stamping: slug prefix is always the function name
pressSequentially stamps as pressSequentially1 (not type1), autoZoom as
autoZoom1, selectOption as selectOption1.
2026-07-08 21:11:13 +03:00
Olli Paloviita 24892e96f0 editId renames: web rename record applied to code by sync
New renameEdit timeline record { target: { editId }, newEditId }.
screenci sync and dev --sync apply it by replacing the slug string
literal (renames run last so earlier edits key on the old slug);
invalid or unlocatable renames fall through to the agent prompt.
Slugs validate as [A-Za-z0-9_-]+.
2026-07-08 20:44:11 +03:00
Olli Paloviita b18aca1619 editId: stable human-readable identity for editable actions
Actions and autoZoom blocks accept an optional editId slug (click1,
zoom2). screenci sync and dev --sync stamp missing slugs automatically
using the call sites captured at record time, allocating from the
committed .screenci/edit-ids.json (numbers never reused, ids never
removed). With an editId the stable key IS the slug, so editor edits
and placed-event anchors survive refactors, moved lines, and locator
changes; sync locates call sites by exact slug instead of heuristics.
Loop executions key as slug#N and stay web-runtime-only.

Also: record --no-render uploads the recording and editable data
without dispatching a render, for a fast first editor sync.
2026-07-08 20:03:02 +03:00
Olli Paloviita d55b5b3cf1 Add screenci sync: apply web editor edits to sources via static analysis
New command applies action-option changes, sleepBefore waits, autoZoom
offsets, and placeHide/Speed/Time/Zoom calls directly to .screenci.ts
files using the TypeScript parser with raw text splices (formatting and
comments preserved). Anything ambiguous (loops, spreads, stale edits,
narration content) falls back to the existing agent sync prompt.
Dry-run diff by default; --write saves, --write --reset clears web
edits for fully applied videos.
2026-07-08 18:56:32 +03:00
Olli Paloviita e9f961c37e sync-prompt: include Studio render/record option overrides and content notes
Add a /cli/studio-sync fetch and a pure studioSync builder so screenci
sync-prompt emits SET directives for the render and record options the web
editor holds, plus NOTE lines for narration/text/audio/overlay edits, letting
an agent codify render options into the scripts too.
2026-07-08 18:03:59 +03:00
Olli Paloviita 3e9f477430 Model background/narration-box/recording updates as unified placed events
Add three point kinds (background, narrationBox, recording) to the
timeline-edits wire format so the web editor can place presentation
updates without a legacy authored path. applyPlacedEvents inserts the
matching backgroundUpdate/narrationUpdate/recordingUpdate events, and
screenci status/sync-prompt now apply the -g grep filter to the
timeline-edits and placed-events blocks too.
2026-07-08 14:31:04 +03:00
Olli Paloviita 8b99f49b9a WIP: park uncommitted timeline-edits work from main checkout 2026-07-08 12:53:05 +03:00
Olli Paloviita 0702ab1661 Timeline v2 SDK: full editable coverage, navigation borders, source capture
- hide('name', fn) and time('name', ms, fn) overloads with editable metadata
  (hide read-only span; time durationMs web-editable with shadow warnings)
- resetZoom() editable (easing/duration); page.goto emits navigation marker
  events (hard borders, app time, never editable)
- redact() emits a marker event with web-editable mask styling (color,
  radius, css) applied at the next record
- Presentation updates editable: moveNarration/resizeNarration,
  resizeRecording/hideRecording/showRecording, setBackground (corner, size,
  css, transition duration)
- Editable descriptors capture the user-code call site (file:line) so
  sync-prompt can emit exact placement instructions; snapshot carries it
- Authored events: span kind 'time' (render-time remap) and point kinds
  narrationUpdate/recordingUpdate/backgroundUpdate
- sync-prompt v2: CHANGE/INSERT/WRAP placement instructions with call sites
  and a ripple-scope note; screenci reset-web-edits clears the web layer
- Docs: what is editable from the web, resetting web edits
2026-07-08 01:18:34 +03:00
Olli Paloviita 2ba5ff16b1 Pre-action sleep, override debug mode, sleepBefore injection
- sleepBefore field on every pointer action: the SDK sleeps that long after
  the previous event before the cursor starts moving, recorded as a leading
  wait so the editor shows it; accepted as an override even for recordings
  made before the field existed in defaults
- SCREENCI_DEBUG_OVERRIDES=1: the CLI dumps every override set fetched from
  the backend (per channel, with explicit '(none)'), enables fetch warnings,
  and the SDK logs each override value and authored event as it is applied
  (debug flag lives in dependency-free debugFlags.ts to avoid import cycles)
- Docs: debugging overrides section, sleepBefore note
2026-07-07 23:20:59 +03:00
Olli Paloviita ac4c494318 Make every interaction web-editable with per-field code provenance
- Per-field lockedFields: explicit code values no longer block web edits;
  overrides apply with a shadow warning at record time and in the editor
- Stamp zoomTo (manual zoom) with editable metadata so zooms show on the
  editor's camera row
- New editable-actions.json snapshot: pre-record shadow warnings and
  screenci status classification (shadows code / changes default / stale)
- Web-authored events: hides and speed blocks anchored to a known event
  (stable key or timestamp name) plus offset, with duration or end anchor;
  applied into data.json at write time, skipped with a warning on broken
  anchors; reported by screenci status
- Web languages channel: fetch /cli/web-languages and union web-added
  languages into each video's recorded set (SCREENCI_WEB_LANGUAGES),
  separate from the restricting --languages filter
- Remove recordOptions.implicitEditable (everything is always editable)
- Docs updated (editor, configuration, languages)
2026-07-07 21:09:55 +03:00
Olli Paloviita 2ba27d40ba Editor overrides for defaults, used-value reporting, status and sync-prompt
Overrides can target defaulted values too; only an override that changes
the used value is logged and recorded (as `used` in actionParams, so the
editor can update its options from what the recording ran with).
ACTION_PARAM_DEFAULTS is exported so the backend can detect overrides
that merely restate a default. The CLI now fetches overrides from
GET /cli/action-overrides by default (404 = none), and gains
`screenci status` (compare editor edits with the latest run) and
`screenci sync-prompt -g <regex>` (agent-ready change/remove
instructions to bring code back in sync).
2026-07-07 17:30:29 +03:00
Olli Paloviita b8df6725f3 Merge language-specific render/record options and timeline work into editable() removal
# Conflicts:
#	cli.ts
#	docs/configuration.md
#	docs/editor.md
#	docs/video-script-basics.md
#	src/builder.spec.ts
#	src/builder.ts
#	src/events.ts
#	src/instrument.spec.ts
#	src/instrument.ts
#	src/screenshot.ts
#	src/studio.ts
#	src/types.ts
#	src/video.ts
2026-07-07 16:43:23 +03:00
Olli Paloviita f335f3e800 Remove editable(): everything is web-editable; track action param provenance
Breaking change: the editable() marker is gone. Arrays declare blank
editor-owned names, plain objects are code values the web app may
override, and every video.languages(...) set is web-owned (union of web
selection, code seed, and per-feature keys). Render/record options are
always studio-flagged.

New: every instrumented action records which option values were explicit
at the call site vs defaults. The provenance rides in data.json
(actionParams), is snapshotted to .screenci/action-params.json (never
wiped), and editor overrides (SCREENCI_ACTION_OVERRIDES, fetched behind
the stubbed ActionOverridesClient) are applied during recording with an
info line per application plus a record-start warning when an override
shadows an explicit code value.
2026-07-07 16:05:06 +03:00
Olli Paloviita 50273429dc feat: language-specific renderOptions and recordOptions builder methods
Replace video.use({ renderOptions, recordOptions }) with dedicated
chainable builder methods video.renderOptions(...) / video.recordOptions(...)
(and the screenshot counterparts). They accept a flat object (shared across
languages), a language-major object ({ default, de, ... }) for per-language
overrides, or editable() to hand the bag to the web app, mirroring
video.narration(...).

- Add src/optionsDeclare.ts: normalization (flat / language-major / editable)
  reusing declare.ts disambiguation, plus deep-merge helpers (flat
  mergeRecordOptions, group-wise mergeRenderOptions) and layer combiners.
- builder.ts: pre-merge per-pass options (shared base, language override,
  each-variant patch) onto internal fixtures; throw on per-language options
  with mode: 'shared'; warn on unused language keys.
- video.ts / screenshot.ts: replace the public recordOptions/renderOptions
  fixtures with internal ones and combine the config-level default layer with
  the per-video builder patch at each consumption site. Fixes an existing bug
  where an each-variant recordOptions clobbered config-level options instead
  of merging.
- config.ts: defineConfig remaps use.recordOptions/renderOptions onto the
  internal fixtures, keeping config as the project-wide default layer.
- Migrate docs, doc-source scripts, SKILL, and voices/localize JSDoc.

Tests: add optionsDeclare and builder coverage (merging, editable, shared-mode
throw, each-variant patch); update config remap assertions. Full unit suite
(1312) and e2e (104) pass.
2026-07-07 14:19:04 +03:00
Olli Paloviita c97b1876c5 feat: web-editable timeline actions (descriptors, overrides, delay events)
Capture editable-action descriptors (locator matcher, ordinal, seq) on
input/autoZoom/speed events, add delay events for waitForTimeout, add
recordOptions.implicitEditable, editable('name', defaults) overloads,
speed(name|fn) overloads, waitForTimeout()/editable forms, runtime
override application via SCREENCI_EDITABLE_OVERRIDES, and the CLI
pre-fetch of /cli/editable-overrides.
2026-07-07 13:06:07 +03:00
Olli Paloviita 733103b1d7 docs: refresh Screenci guides and manifest 2026-07-06 04:08:42 +03:00
Olli Paloviita bba87b8af4 Update Screenci editor docs and recording APIs 2026-07-05 21:36:03 +03:00