Android Shard Editor: Clock moves shard to now #340

Closed
opened 2026-10-26 19:41:17 +01:00 by kfickel · 2 comments
Owner

Change the little clock icon in the editor as follows:

In the shard the pointer currently is on, add the date and time shards necessary to move the current shard to now.

For example if the shard would be 12:40:00, but now is 13:00:00, insert @130000 after the # of the heading (if that's the start of the shard we would have been in) - same for the date if it doesn't fit.

Change the little clock icon in the editor as follows: In the shard the pointer currently is on, add the date and time shards necessary to move the current shard to now. For example if the shard would be 12:40:00, but now is 13:00:00, insert @130000 after the # of the heading (if that's the start of the shard we would have been in) - same for the date if it doesn't fit.
Collaborator

Plan

Behaviour (decided)

  • Tapping the clock in the bar above the keyboard of the editor (Capture.kt, onTime) moves the shard the cursor is in to now. The old "insert @HHMMSS at the cursor" goes away as the tap's action and stays only as the fallback below.
  • The shard is the innermost shard whose start_line..=end_line holds the line of the cursor.
  • Its own moment markers are removed from its first line (time @HHMMSS, date @YYYYMMDD, datetime @YYYYMMDDHHMMSS, those that is_moment_marker accepts among its markers). Then the markers it needs to read as now, relative to what it inherits, are added, as two markers like append: by moment_markers(inherited, now, tz) of commands/file/append.rs. This gives @HHMMSS if the date matches, @YYYYMMDD @HHMMSS if not, and nothing if it already reads as now.
    • Example: under a file 20261026-… with # @124000 Call at 13:00:00, the result is # @130000 Call. On another day it would be # @20261026 @130000 Call.
  • Where the markers go: at the position of the first removed moment marker. If there was none, they go after the other leading markers (as heading() of append.rs does), so they stay markers: # @Task @130000 Call, - @130000 item, plain paragraph @130000 text. The indentation and the #/- stay as they are, and single spaces are kept (as with_whitespace in state/edit.rs does).
  • Inherited moment: the moment of the parent shard, folded from the root down with extract_datetime_from_marker_list. The root starts from extract_datetime_from_file_name(name, tz), or from now if the name has no date (e.g. an unsaved note without one).
  • Root shard: if the cursor is in the file's root shard and its markers are not on a heading on its first line (text before the first heading, or no heading at all), fall back to the old behaviour: insert the markers moment_markers(file moment, now) gives (at least @HHMMSS) at the cursor, via insertingMarker. If the root takes its markers from a tagged heading on line 1, that line is edited like any other shard, relative to the file name's moment.
  • Children without their own moment markers move with the shard (they inherit). This is intended.
  • The cursor stays in the same text: it is shifted by the length change if it was after the edit on that line.

Core (streamd-core)

  1. New pure function in document/ (e.g. document/moment.rs): move_to_now(text: &str, file_name: &str, offset: usize, now: DateTime<Utc>, tz: Tz) -> MomentEdit. It parses the text with extract::parser, finds the shard, computes what it inherits, and returns:
    • MomentEdit::Line { range: Range<usize>, replacement: String }: the byte range of the shard's first line (or a smaller span) and its new text, or
    • MomentEdit::AtCursor { markers: Vec<String> } for the root fallback, or
    • MomentEdit::None if it already reads as now.
      It reuses marker_spans / with_whitespace of commands/state/edit.rs (make with_whitespace pub(crate)) and moment_markers of append.rs (move it to localize/datetime.rs if that reads better, since it is now shared).
  2. api.rs: move_to_now(...) in the facade.
  3. Tests on texts with a heading, a nested heading, a list item, a paragraph, existing time / date / datetime markers, a different day, a shard already at now, the root fallback, a tagged heading on line 1, an undated file name, and a DST gap (the moment goes through local_to_utc).

Bindings (streamd-ffi)

  • moveToNow(text, fileName, offset, now, tz) as a free function, with the offsets converted through text.rs (bytes ↔ UTF-16). It returns a record/enum MomentEdit (Line { range: TextRange, replacement }, AtCursor { markers }, Unchanged). Add a conversion test.

App

  • StreamSession.momentEdit(name, text, cursor, now): the only call into the bindings. Expose it through Capturing (with the moment of the call from CaptureAdapter), and use plain app types in core/Capture.kt.
  • Editing.kt: a pure Typed.applying(edit) that splices a Line edit and moves the cursor, and uses insertingMarker for each marker of AtCursor. Unit-test it in EditingTest.kt.
  • Capture.kt: onTime asks Capturing with the draft's current name (Draft.named/destination name) and text, then applies the result. Change the label/description of the tool (tool_time in strings.xml) to something like "Move to now".
  • The screenshots don't change unless the label is visible. If it is, update the references.

Docs

  • REQUIREMENTS.md (the requirement of the editor of the app, R35-area / capture), README.md (the editor's toolbar), and AGENTS.md (document/ module list, the facade list, Editing.kt, Capturing).
## Plan ### Behaviour (decided) - Tapping the clock in the bar above the keyboard of the editor (`Capture.kt`, `onTime`) **moves the shard the cursor is in to now**. The old "insert `@HHMMSS` at the cursor" goes away as the tap's action and stays only as the fallback below. - The shard is the **innermost** shard whose `start_line..=end_line` holds the line of the cursor. - Its **own moment markers are removed** from its first line (time `@HHMMSS`, date `@YYYYMMDD`, datetime `@YYYYMMDDHHMMSS`, those that `is_moment_marker` accepts among its markers). Then the markers it needs to read as now, **relative to what it inherits**, are added, as **two markers like `append`**: by `moment_markers(inherited, now, tz)` of `commands/file/append.rs`. This gives `@HHMMSS` if the date matches, `@YYYYMMDD @HHMMSS` if not, and nothing if it already reads as now. - Example: under a file `20261026-…` with `# @124000 Call` at 13:00:00, the result is `# @130000 Call`. On another day it would be `# @20261026 @130000 Call`. - **Where the markers go:** at the position of the first removed moment marker. If there was none, they go after the other leading markers (as `heading()` of `append.rs` does), so they stay markers: `# @Task @130000 Call`, `- @130000 item`, plain paragraph `@130000 text`. The indentation and the `#`/`- ` stay as they are, and single spaces are kept (as `with_whitespace` in `state/edit.rs` does). - **Inherited moment:** the moment of the parent shard, folded from the root down with `extract_datetime_from_marker_list`. The root starts from `extract_datetime_from_file_name(name, tz)`, or from `now` if the name has no date (e.g. an unsaved note without one). - **Root shard:** if the cursor is in the file's root shard and its markers are not on a heading on its first line (text before the first heading, or no heading at all), **fall back to the old behaviour**: insert the markers `moment_markers(file moment, now)` gives (at least `@HHMMSS`) at the cursor, via `insertingMarker`. If the root takes its markers from a tagged heading on line 1, that line is edited like any other shard, relative to the file name's moment. - Children without their own moment markers move with the shard (they inherit). This is intended. - The cursor stays in the same text: it is shifted by the length change if it was after the edit on that line. ### Core (`streamd-core`) 1. New pure function in `document/` (e.g. `document/moment.rs`): `move_to_now(text: &str, file_name: &str, offset: usize, now: DateTime<Utc>, tz: Tz) -> MomentEdit`. It parses the text with `extract::parser`, finds the shard, computes what it inherits, and returns: - `MomentEdit::Line { range: Range<usize>, replacement: String }`: the byte range of the shard's first line (or a smaller span) and its new text, or - `MomentEdit::AtCursor { markers: Vec<String> }` for the root fallback, or - `MomentEdit::None` if it already reads as now. It reuses `marker_spans` / `with_whitespace` of `commands/state/edit.rs` (make `with_whitespace` `pub(crate)`) and `moment_markers` of `append.rs` (move it to `localize/datetime.rs` if that reads better, since it is now shared). 2. `api.rs`: `move_to_now(...)` in the facade. 3. Tests on texts with a heading, a nested heading, a list item, a paragraph, existing time / date / datetime markers, a different day, a shard already at now, the root fallback, a tagged heading on line 1, an undated file name, and a DST gap (the moment goes through `local_to_utc`). ### Bindings (`streamd-ffi`) - `moveToNow(text, fileName, offset, now, tz)` as a free function, with the offsets converted through `text.rs` (bytes ↔ UTF-16). It returns a record/enum `MomentEdit` (`Line { range: TextRange, replacement }`, `AtCursor { markers }`, `Unchanged`). Add a conversion test. ### App - `StreamSession.momentEdit(name, text, cursor, now)`: the only call into the bindings. Expose it through `Capturing` (with the moment of the call from `CaptureAdapter`), and use plain app types in `core/Capture.kt`. - `Editing.kt`: a pure `Typed.applying(edit)` that splices a `Line` edit and moves the cursor, and uses `insertingMarker` for each marker of `AtCursor`. Unit-test it in `EditingTest.kt`. - `Capture.kt`: `onTime` asks `Capturing` with the draft's current name (`Draft.named`/destination name) and text, then applies the result. Change the label/description of the tool (`tool_time` in `strings.xml`) to something like "Move to now". - The screenshots don't change unless the label is visible. If it is, update the references. ### Docs - `REQUIREMENTS.md` (the requirement of the editor of the app, R35-area / capture), `README.md` (the editor's toolbar), and `AGENTS.md` (`document/` module list, the facade list, `Editing.kt`, `Capturing`).
Collaborator

Implemented in PR #342.

Time: about 30 minutes (21:36–22:07 UTC), including the full app build with unit and screenshot tests.
Tokens: about 75k.

Findings during the implementation:

  • Untagged headings are not shards. The parser only splits at headings where some heading carries markers. So "the shard at the cursor" alone would often mean the whole file or an enclosing section. The clock therefore uses the nearest heading between the innermost shard's start and the cursor; adding a marker turns that heading into a shard.
  • A marker on line 1 belongs to the file. When the first block of a file has markers, they go on the file's root shard. Marking an unmarked shard on line 1 would therefore move the whole file. That case uses the old "insert at cursor" behaviour, as decided in the plan.
  • Marking can change what a shard inherits. Marking a heading can split the file anew, so the shard may inherit from another one than before (e.g. the date of a first tagged heading no longer applies). After the edit the text is parsed again, and if the shard doesn't read as now, it gets both the date and the time.
  • At least the time is always written. With an undated file name, or a shard whose inherited moment already equals now, removing its markers would turn it back into plain text. So at least @HHMMSS is always kept.
  • Seconds only. now is truncated to whole seconds before comparing, since markers carry no sub-second part.
  • Smaller points:
    • The bindings take a UTF-16 cursor and return the range and new cursor in UTF-16 units; this is tested with umlauts and an emoji.
    • The clock now asks the core, which is asynchronous, so the app only applies the change if the text wasn't edited meanwhile.
    • Without a read stream it falls back to the time at the cursor.
  • No new dependencies. The screenshots are unchanged; the label "Move to now" is only a content description.
Implemented in PR #342. **Time:** about 30 minutes (21:36–22:07 UTC), including the full app build with unit and screenshot tests. **Tokens:** about 75k. **Findings during the implementation:** - **Untagged headings are not shards.** The parser only splits at headings where some heading carries markers. So "the shard at the cursor" alone would often mean the whole file or an enclosing section. The clock therefore uses the nearest heading between the innermost shard's start and the cursor; adding a marker turns that heading into a shard. - **A marker on line 1 belongs to the file.** When the first block of a file has markers, they go on the file's root shard. Marking an unmarked shard on line 1 would therefore move the whole file. That case uses the old "insert at cursor" behaviour, as decided in the plan. - **Marking can change what a shard inherits.** Marking a heading can split the file anew, so the shard may inherit from another one than before (e.g. the date of a first tagged heading no longer applies). After the edit the text is parsed again, and if the shard doesn't read as now, it gets both the date and the time. - **At least the time is always written.** With an undated file name, or a shard whose inherited moment already equals now, removing its markers would turn it back into plain text. So at least `@HHMMSS` is always kept. - **Seconds only.** `now` is truncated to whole seconds before comparing, since markers carry no sub-second part. - **Smaller points:** - The bindings take a UTF-16 cursor and return the range and new cursor in UTF-16 units; this is tested with umlauts and an emoji. - The clock now asks the core, which is asynchronous, so the app only applies the change if the text wasn't edited meanwhile. - Without a read stream it falls back to the time at the cursor. - **No new dependencies.** The screenshots are unchanged; the label "Move to now" is only a content description.
Sign in to join this conversation.
No labels
android
cli
epic
planned
tui
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
kfickel/streamd#340
No description provided.