From 5e462f881d648eb0183b6dbf155c664aa519ccc4 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 2 Sep 2026 05:05:59 +0200 Subject: [PATCH] Release Time Tools 0.2.0 --- .gitea/workflows/verify.yml | 39 ++ CHANGELOG.md | 5 + README.md | 20 +- SOURCE.md | 4 +- THIRD_PARTY_NOTICES.md | 8 +- docs/ARCHITECTURE.md | 8 +- docs/PRIVACY-SECURITY.md | 8 +- package-lock.json | 45 +- package.json | 10 +- playwright.config.ts | 22 +- public/CHANGELOG.md | 5 + public/LICENSES/npm-runtime-licenses.txt | 6 +- public/README.md | 20 +- public/SOURCE.md | 4 +- public/THIRD_PARTY_NOTICES.md | 8 +- public/docs/ARCHITECTURE.md | 8 +- public/docs/PRIVACY-SECURITY.md | 8 +- public/sw.js | 2 +- public/toolbox-app.json | 25 +- src/components/Workbench.tsx | 218 +++++- src/styles.css | 2 +- src/time/ics-inspect.ts | 815 +++++++++++++++++++++++ src/time/ics.ts | 2 +- src/time/meeting.ts | 175 +++++ src/time/recurrence.ts | 141 +++- src/toolbox/manifest.source.json | 25 +- src/version.ts | 2 +- tests/browser/app.spec.ts | 61 +- tests/browser/responsive.spec.ts | 18 + tests/time/core.test.ts | 226 ++++++- 30 files changed, 1830 insertions(+), 110 deletions(-) create mode 100644 .gitea/workflows/verify.yml create mode 100644 src/time/ics-inspect.ts create mode 100644 src/time/meeting.ts create mode 100644 tests/browser/responsive.spec.ts diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..b84a7cb --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,39 @@ +name: Verify + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: verify-${{ gitea.repository }}-${{ gitea.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + CI: "true" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + - name: Select declared npm version + run: npm install --global npm@11.17.0 + - name: Install dependencies + run: npm ci + - name: Audit runtime dependencies + run: npm audit --omit=dev --audit-level=moderate + - name: Check, test, and build + run: npm run check + - name: Install browser engines + run: npx playwright install --with-deps chromium firefox webkit + - name: Browser tests + run: npm run test:browser diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c48a0f..c4a7abe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added a bounded multi-participant IANA-zone meeting planner based on explicit recurring work windows. +- Added explicit Unix/Vixie, GitHub Actions, Croner, Quartz, and AWS EventBridge cron dialect validation, normalized previews and scheduler caveats, plus iCalendar Toolbox I/O metadata. + ## 0.1.0 - 2026-09-01 - Added the initial local-first Time Tools workbench. diff --git a/README.md b/README.md index ce7356d..5b541b7 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,31 @@ # Time Tools -Work with dates, time zones and recurrences locally in the browser. +Plan across time zones and work with dates and recurrences locally in the browser. Time Tools is a standalone local-first application in the [add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are processed in the browser and are not uploaded. -## Version 0.1 scope +## Version 0.2 scope - Exact signed epoch conversion through nanosecond precision - IANA-zone comparison, skipped/ambiguous local-time resolution and transition inspection - Side-by-side wall-clock and elapsed-duration arithmetic -- Bounded 5/6/7-field cron and strict RFC 5545 RRULE occurrence previews +- Bounded cron previews with explicit Unix/Vixie, GitHub Actions, Croner, Quartz and AWS EventBridge dialect rules, plus strict RFC 5545 RRULE previews +- A bounded multi-participant meeting planner that compares entered recurring local work windows across IANA zones - Weekend/explicit-holiday business-day arithmetic and escaped UTC iCalendar event export +- Bounded `.ics` file/paste import with structural validation, multiple-event inventory, VTIMEZONE inspection and recurrence previews that apply EXDATE/RDATE plus moved or cancelled RECURRENCE-ID overrides +- Inert URL and attachment inspection: references are reported as data and are never fetched, opened, decoded or rendered + +iCalendar import is limited to 8 MiB, 100,000 content properties, 20,000 +components, 10,000 events, 16 component levels and 1,000 previewed +occurrences. Named TZIDs available to the browser are evaluated with its IANA +data. Custom VTIMEZONE observances are inventoried, but recurrence expansion is +withheld when the browser cannot execute that TZID rather than guessing an +offset. Named-zone results use the IANA data supplied by the browser runtime and can therefore vary with browser versions. ECMAScript Temporal does not represent leap seconds. The business-day calculator excludes weekends and only the holiday dates supplied by the user; it does not infer regional calendars. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). +Cron previews intentionally reject fields or extensions outside the selected dialect and show the normalized expression and scheduler caveats. The meeting planner reads no calendar, holiday, travel or presence data: every candidate comes only from the work windows and weekend choices entered in the page. + ## Development Requires Node.js 22 and npm 11. @@ -26,7 +38,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/time-tools-0.1.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/time-tools-0.2.0.zip` and checksum sidecar. ## Licence diff --git a/SOURCE.md b/SOURCE.md index 82392c8..631665f 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Time Tools 0.1.0 is available at: +The corresponding source for Time Tools 0.2.0 is available at: -https://git.add-ideas.de/lotobo/time-tools/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/time-tools/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 66bc509..b714f12 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Time Tools 0.1.0 directly depends on these runtime packages: +Time Tools 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `croner` | 10.0.1 | MIT | | `rrule-temporal` | 2.2.2 | MIT | | `temporal-polyfill` | 1.0.4 | MIT | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 3e38f7d..f6bcc25 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,7 +1,11 @@ # Architecture -Time Tools is a static React/Vite application wrapped in the shared Toolbox shell. Its pure modules separate exact epoch parsing, named-zone resolution/transition inspection, calendar-versus-elapsed arithmetic, bounded recurrence preview, business-day calculation and UTC iCalendar generation. +Time Tools is a static React/Vite application wrapped in the shared Toolbox shell. Its pure modules separate exact epoch parsing, named-zone resolution/transition inspection, calendar-versus-elapsed arithmetic, bounded recurrence preview, business-day calculation, UTC iCalendar generation and bounded iCalendar inspection. `temporal-polyfill` supplies Temporal semantics while the browser runtime supplies IANA time-zone data. Croner handles explicitly selected 5/6/7-field cron syntax; `rrule-temporal` parses strict RFC 5545 recurrence rules with bounded iterations/candidate evaluations. iCalendar export resolves one named-zone wall time and writes escaped/folded UTC DTSTART/DTEND fields rather than an incomplete `VTIMEZONE`. -Version 0.1 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. +`time/recurrence.ts` places an explicit dialect adapter in front of Croner. It validates field counts, portable subsets, UTC-only schedulers, day-of-month/day-of-week semantics and scheduler-specific caveats before generating a bounded preview. `time/meeting.ts` advances a bounded instant range at a declared step, projects each candidate into every participant's IANA zone, and accepts it only when the fixed elapsed interval stays inside all entered same-day work windows. + +`time/ics-inspect.ts` unfolds and tokenises imported calendars into a bounded component tree, validates calendar/event structure, inventories `VTIMEZONE` observances, and previews multiple recurring events with `RDATE`, `EXDATE`, and moved or cancelled `RECURRENCE-ID` overrides. It preserves floating/all-day semantics during preview. Unsupported custom-zone recurrence and `RANGE=THISANDFUTURE` overrides produce diagnostics and withheld occurrences instead of guessed instants. URLs and attachments remain inert metadata records. + +Version 0.2 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. diff --git a/docs/PRIVACY-SECURITY.md b/docs/PRIVACY-SECURITY.md index 3c2fe1d..96049fd 100644 --- a/docs/PRIVACY-SECURITY.md +++ b/docs/PRIVACY-SECURITY.md @@ -1,7 +1,9 @@ # Privacy and security -Time values, schedules and calendar fields stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Exported ICS text is escaped and UTF-8 line-folded; users choose whether to download/import it into another application. +Time values, schedules and imported calendar fields stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Exported ICS text is escaped and UTF-8 line-folded; users choose whether to download/import it into another application. Imported URL and ATTACH values are displayed only as inert text/JSON: URIs are never followed and embedded Base64 is inventoried rather than decoded. -Epoch input uses signed integer arithmetic and rejects exponent/fraction syntax. Recurrence output is limited to 1,000 occurrences, RRULE evaluation uses hard iteration/candidate bounds, business-day movement is limited to ±100,000 days, and explicit holidays to 10,000 bounded ISO dates. Calendar text fields and zone/duration inputs are length-bounded before parsing. +Epoch input uses signed integer arithmetic and rejects exponent/fraction syntax. Recurrence output is limited to 1,000 occurrences, RRULE evaluation uses hard iteration/candidate bounds, business-day movement is limited to ±100,000 days, and explicit holidays to 10,000 bounded ISO dates. Calendar imports are limited by source size, line/property/component/event counts, component depth, unfolded-line length and occurrence count; calendar text fields and zone/duration inputs are length-bounded before parsing. -Named-zone results depend on the IANA data supplied by the browser/runtime and can vary across versions. Temporal does not represent leap seconds. Cron semantics vary by implementation; the selected field count and common Unix day-of-month/day-of-week OR rule are shown. Business days mean Monday–Friday minus only the dates entered, not a regional legal calendar. +Named-zone results depend on the IANA data supplied by the browser/runtime and can vary across versions. Temporal does not represent leap seconds. Cron semantics vary by implementation; the chosen dialect, exact field count, normalized expression and day-field rule are shown, while remote delivery/misfire behavior is explicitly outside the preview. Business days mean Monday–Friday minus only the dates entered, not a regional legal calendar. + +Meeting candidates use only the entered participant names, IANA zones, work windows and weekend flags. The search is capped at 24 participants, 31 days and 100 results and performs no calendar lookup. A candidate is scheduling arithmetic, not evidence that a person is actually available. diff --git a/package-lock.json b/package-lock.json index dfbe59a..dd4379f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "time-tools", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "time-tools", - "version": "0.1.0", + "version": "0.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "croner": "10.0.1", "react": "19.2.8", "react-dom": "19.2.8", @@ -19,7 +19,7 @@ "temporal-polyfill": "1.0.4" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", @@ -45,24 +45,24 @@ } }, "node_modules/@add-ideas/toolbox-contract": { - "version": "0.2.3", - "license": "Apache-2.0", - "engines": { - "node": ">=20" - } + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.3.0/toolbox-contract-0.3.0.tgz", + "integrity": "sha512-dKrK7BjOFwqJaBfJuhKxZKIld4sH0AKjEn6a0yLnbdMUFY+fFv4VSLGV2tNSBD016gumc2iNqOjUj/ld7x4rtA==", + "license": "Apache-2.0" }, "node_modules/@add-ideas/toolbox-helpers": { - "version": "0.1.0", - "license": "GPL-3.0-or-later", - "engines": { - "node": ">=22" - } + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-helpers/-/0.2.0/toolbox-helpers-0.2.0.tgz", + "integrity": "sha512-SdOqkw+P+3J3fa5iVkzb5P15rVepB001GNV21Oh8w0CZcVL+YRltgD/s+MVcTyrNijWQf3E5vtQON/3N2LLyKg==", + "license": "GPL-3.0-or-later" }, "node_modules/@add-ideas/toolbox-shell-react": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.3.0/toolbox-shell-react-0.3.0.tgz", + "integrity": "sha512-74p6JzAOG0YCAKdlc1hLofV4ZIko7vb448S75cIiM88PKm93EHl5VD7g8YVyfM56Ui97UY9dmy+Whiq4sGzpsg==", "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "peerDependencies": { "react": ">=18 <20", @@ -70,17 +70,16 @@ } }, "node_modules/@add-ideas/toolbox-testkit": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.3.0/toolbox-testkit-0.3.0.tgz", + "integrity": "sha512-4Fk+oSvZFspOMIXr8Xy040nhAaBsIQAzsGyXWSpjn3+k3yBKq7nB1r5zCHhsXzfdLzvPDAx2KcmSNOhM330D9w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "bin": { "toolbox-check": "dist/cli.js" - }, - "engines": { - "node": ">=20" } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index a76c244..71c45ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tools", - "version": "0.1.0", + "version": "0.2.0", "description": "Work with dates, time zones and recurrences locally in the browser.", "license": "GPL-3.0-or-later", "author": "Albrecht Degering", @@ -39,9 +39,9 @@ "release:artifact": "npm run check && npm run test:browser && npm run package:release -- --force" }, "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", "croner": "10.0.1", "react": "19.2.8", "react-dom": "19.2.8", @@ -49,7 +49,7 @@ "temporal-polyfill": "1.0.4" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", diff --git a/playwright.config.ts b/playwright.config.ts index 731da62..2ab6a22 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,7 +15,25 @@ export default defineConfig({ timeout: 180_000, }, projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { + name: "chromium", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Safari"] }, + }, + { + name: "mobile-chromium", + testMatch: /responsive\.spec\.ts/, + use: { ...devices["Pixel 5"] }, + }, ], }); diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index 8c48a0f..c4a7abe 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added a bounded multi-participant IANA-zone meeting planner based on explicit recurring work windows. +- Added explicit Unix/Vixie, GitHub Actions, Croner, Quartz, and AWS EventBridge cron dialect validation, normalized previews and scheduler caveats, plus iCalendar Toolbox I/O metadata. + ## 0.1.0 - 2026-09-01 - Added the initial local-first Time Tools workbench. diff --git a/public/LICENSES/npm-runtime-licenses.txt b/public/LICENSES/npm-runtime-licenses.txt index e15e502..fc149d1 100644 --- a/public/LICENSES/npm-runtime-licenses.txt +++ b/public/LICENSES/npm-runtime-licenses.txt @@ -1,5 +1,5 @@ ============================================================================== -@add-ideas/toolbox-contract@0.2.3 +@add-ideas/toolbox-contract@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- @@ -198,7 +198,7 @@ Declared licence: Apache-2.0 ============================================================================== -@add-ideas/toolbox-helpers@0.1.0 +@add-ideas/toolbox-helpers@0.2.0 Declared licence: GPL-3.0-or-later ============================================================================== --- LICENSE --- @@ -879,7 +879,7 @@ Public License instead of this License. But first, please read ============================================================================== -@add-ideas/toolbox-shell-react@0.2.3 +@add-ideas/toolbox-shell-react@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- diff --git a/public/README.md b/public/README.md index ce7356d..5b541b7 100644 --- a/public/README.md +++ b/public/README.md @@ -1,19 +1,31 @@ # Time Tools -Work with dates, time zones and recurrences locally in the browser. +Plan across time zones and work with dates and recurrences locally in the browser. Time Tools is a standalone local-first application in the [add·ideas Toolbox](https://git.add-ideas.de/lotobo/toolbox-portal). Inputs are processed in the browser and are not uploaded. -## Version 0.1 scope +## Version 0.2 scope - Exact signed epoch conversion through nanosecond precision - IANA-zone comparison, skipped/ambiguous local-time resolution and transition inspection - Side-by-side wall-clock and elapsed-duration arithmetic -- Bounded 5/6/7-field cron and strict RFC 5545 RRULE occurrence previews +- Bounded cron previews with explicit Unix/Vixie, GitHub Actions, Croner, Quartz and AWS EventBridge dialect rules, plus strict RFC 5545 RRULE previews +- A bounded multi-participant meeting planner that compares entered recurring local work windows across IANA zones - Weekend/explicit-holiday business-day arithmetic and escaped UTC iCalendar event export +- Bounded `.ics` file/paste import with structural validation, multiple-event inventory, VTIMEZONE inspection and recurrence previews that apply EXDATE/RDATE plus moved or cancelled RECURRENCE-ID overrides +- Inert URL and attachment inspection: references are reported as data and are never fetched, opened, decoded or rendered + +iCalendar import is limited to 8 MiB, 100,000 content properties, 20,000 +components, 10,000 events, 16 component levels and 1,000 previewed +occurrences. Named TZIDs available to the browser are evaluated with its IANA +data. Custom VTIMEZONE observances are inventoried, but recurrence expansion is +withheld when the browser cannot execute that TZID rather than guessing an +offset. Named-zone results use the IANA data supplied by the browser runtime and can therefore vary with browser versions. ECMAScript Temporal does not represent leap seconds. The business-day calculator excludes weekends and only the holiday dates supplied by the user; it does not infer regional calendars. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). +Cron previews intentionally reject fields or extensions outside the selected dialect and show the normalized expression and scheduler caveats. The meeting planner reads no calendar, holiday, travel or presence data: every candidate comes only from the work windows and weekend choices entered in the page. + ## Development Requires Node.js 22 and npm 11. @@ -26,7 +38,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/time-tools-0.1.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/time-tools-0.2.0.zip` and checksum sidecar. ## Licence diff --git a/public/SOURCE.md b/public/SOURCE.md index 82392c8..631665f 100644 --- a/public/SOURCE.md +++ b/public/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Time Tools 0.1.0 is available at: +The corresponding source for Time Tools 0.2.0 is available at: -https://git.add-ideas.de/lotobo/time-tools/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/time-tools/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/public/THIRD_PARTY_NOTICES.md b/public/THIRD_PARTY_NOTICES.md index 66bc509..b714f12 100644 --- a/public/THIRD_PARTY_NOTICES.md +++ b/public/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Time Tools 0.1.0 directly depends on these runtime packages: +Time Tools 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `croner` | 10.0.1 | MIT | | `rrule-temporal` | 2.2.2 | MIT | | `temporal-polyfill` | 1.0.4 | MIT | diff --git a/public/docs/ARCHITECTURE.md b/public/docs/ARCHITECTURE.md index 3e38f7d..f6bcc25 100644 --- a/public/docs/ARCHITECTURE.md +++ b/public/docs/ARCHITECTURE.md @@ -1,7 +1,11 @@ # Architecture -Time Tools is a static React/Vite application wrapped in the shared Toolbox shell. Its pure modules separate exact epoch parsing, named-zone resolution/transition inspection, calendar-versus-elapsed arithmetic, bounded recurrence preview, business-day calculation and UTC iCalendar generation. +Time Tools is a static React/Vite application wrapped in the shared Toolbox shell. Its pure modules separate exact epoch parsing, named-zone resolution/transition inspection, calendar-versus-elapsed arithmetic, bounded recurrence preview, business-day calculation, UTC iCalendar generation and bounded iCalendar inspection. `temporal-polyfill` supplies Temporal semantics while the browser runtime supplies IANA time-zone data. Croner handles explicitly selected 5/6/7-field cron syntax; `rrule-temporal` parses strict RFC 5545 recurrence rules with bounded iterations/candidate evaluations. iCalendar export resolves one named-zone wall time and writes escaped/folded UTC DTSTART/DTEND fields rather than an incomplete `VTIMEZONE`. -Version 0.1 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. +`time/recurrence.ts` places an explicit dialect adapter in front of Croner. It validates field counts, portable subsets, UTC-only schedulers, day-of-month/day-of-week semantics and scheduler-specific caveats before generating a bounded preview. `time/meeting.ts` advances a bounded instant range at a declared step, projects each candidate into every participant's IANA zone, and accepts it only when the fixed elapsed interval stays inside all entered same-day work windows. + +`time/ics-inspect.ts` unfolds and tokenises imported calendars into a bounded component tree, validates calendar/event structure, inventories `VTIMEZONE` observances, and previews multiple recurring events with `RDATE`, `EXDATE`, and moved or cancelled `RECURRENCE-ID` overrides. It preserves floating/all-day semantics during preview. Unsupported custom-zone recurrence and `RANGE=THISANDFUTURE` overrides produce diagnostics and withheld occurrences instead of guessed instants. URLs and attachments remain inert metadata records. + +Version 0.2 operations are synchronous and bounded, so the app creates no worker and has no persistence or server API. Relative entry and asset URLs keep the build relocatable below a nested portal path. diff --git a/public/docs/PRIVACY-SECURITY.md b/public/docs/PRIVACY-SECURITY.md index 3c2fe1d..96049fd 100644 --- a/public/docs/PRIVACY-SECURITY.md +++ b/public/docs/PRIVACY-SECURITY.md @@ -1,7 +1,9 @@ # Privacy and security -Time values, schedules and calendar fields stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Exported ICS text is escaped and UTF-8 line-folded; users choose whether to download/import it into another application. +Time values, schedules and imported calendar fields stay in page memory. There is no telemetry, analytics, account, persistence or runtime network path. Exported ICS text is escaped and UTF-8 line-folded; users choose whether to download/import it into another application. Imported URL and ATTACH values are displayed only as inert text/JSON: URIs are never followed and embedded Base64 is inventoried rather than decoded. -Epoch input uses signed integer arithmetic and rejects exponent/fraction syntax. Recurrence output is limited to 1,000 occurrences, RRULE evaluation uses hard iteration/candidate bounds, business-day movement is limited to ±100,000 days, and explicit holidays to 10,000 bounded ISO dates. Calendar text fields and zone/duration inputs are length-bounded before parsing. +Epoch input uses signed integer arithmetic and rejects exponent/fraction syntax. Recurrence output is limited to 1,000 occurrences, RRULE evaluation uses hard iteration/candidate bounds, business-day movement is limited to ±100,000 days, and explicit holidays to 10,000 bounded ISO dates. Calendar imports are limited by source size, line/property/component/event counts, component depth, unfolded-line length and occurrence count; calendar text fields and zone/duration inputs are length-bounded before parsing. -Named-zone results depend on the IANA data supplied by the browser/runtime and can vary across versions. Temporal does not represent leap seconds. Cron semantics vary by implementation; the selected field count and common Unix day-of-month/day-of-week OR rule are shown. Business days mean Monday–Friday minus only the dates entered, not a regional legal calendar. +Named-zone results depend on the IANA data supplied by the browser/runtime and can vary across versions. Temporal does not represent leap seconds. Cron semantics vary by implementation; the chosen dialect, exact field count, normalized expression and day-field rule are shown, while remote delivery/misfire behavior is explicitly outside the preview. Business days mean Monday–Friday minus only the dates entered, not a regional legal calendar. + +Meeting candidates use only the entered participant names, IANA zones, work windows and weekend flags. The search is capped at 24 participants, 31 days and 100 results and performs no calendar lookup. A candidate is scheduling arithmetic, not evidence that a person is actually available. diff --git a/public/sw.js b/public/sw.js index 8364a40..dbbd1c9 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ const CACHE_PREFIX = "time-tools-shell-"; -const CACHE_NAME = CACHE_PREFIX + "0.1.0"; +const CACHE_NAME = CACHE_PREFIX + "0.2.0"; const CORE = ["./", "./manifest.webmanifest", "./favicon.svg"]; self.addEventListener("install", (event) => { event.waitUntil( diff --git a/public/toolbox-app.json b/public/toolbox-app.json index 644f2ca..9e9e26a 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -3,12 +3,12 @@ "schemaVersion": 1, "id": "de.add-ideas.time-tools", "name": "Time Tools", - "version": "0.1.0", - "description": "Work with dates, time zones and recurrences locally in the browser.", + "version": "0.2.0", + "description": "Plan across time zones and preview explicit recurrence dialects locally.", "entry": "./", "icon": "./favicon.svg", "categories": ["time", "developer", "productivity"], - "tags": ["timestamp", "timezone", "cron", "rrule", "ics"], + "tags": ["timestamp", "timezone", "meeting", "cron", "rrule", "ics"], "integration": { "contextVersion": 1, "launchModes": ["navigate", "new-tab"], @@ -21,9 +21,26 @@ "crossOriginIsolated": false, "topLevelContext": false }, + "io": { + "accepts": [ + { + "mediaType": "text/calendar", + "extensions": [".ics"], + "label": "iCalendar" + } + ], + "produces": [ + { + "mediaType": "text/calendar", + "extensions": [".ics"], + "label": "iCalendar event" + } + ] + }, + "capabilities": { "required": [], "optional": ["clipboard-write"] }, "privacy": { "processing": "local", - "fileUploads": false, + "fileUploads": true, "telemetry": false, "label": "Inputs stay in this browser; nothing is uploaded." }, diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx index 975fa36..09f017e 100644 --- a/src/components/Workbench.tsx +++ b/src/components/Workbench.tsx @@ -1,18 +1,30 @@ import { useMemo, useState } from "react"; import { triggerBlobDownload } from "@add-ideas/toolbox-helpers"; -import type { CronMode } from "croner"; import { Temporal } from "temporal-polyfill"; import { addBusinessDays, compareArithmetic } from "../time/arithmetic"; import { inspectEpoch, type EpochUnit } from "../time/epoch"; import { createUtcEvent } from "../time/ics"; -import { previewCron, previewRRule } from "../time/recurrence"; +import { inspectIcs } from "../time/ics-inspect"; +import { planMeetings } from "../time/meeting"; +import { + previewCronDialect, + previewRRule, + type CronDialect, +} from "../time/recurrence"; import { compareTimeZones, nextTransitions, resolveLocalDateTime, } from "../time/zones"; -type Tab = "epoch" | "zones" | "arithmetic" | "recurrence" | "business" | "ics"; +type Tab = + | "epoch" + | "zones" + | "meeting" + | "arithmetic" + | "recurrence" + | "business" + | "ics"; interface Output { title: string; text: string; @@ -23,10 +35,11 @@ interface Output { const tabs: ReadonlyArray = [ ["epoch", "Epoch & ISO"], ["zones", "Time zones & DST"], + ["meeting", "Meeting planner"], ["arithmetic", "Arithmetic"], ["recurrence", "Cron & RRULE"], ["business", "Business days"], - ["ics", "ICS event"], + ["ics", "iCalendar"], ]; function initialInstant(): string { @@ -101,7 +114,7 @@ export function Workbench() { "cron", ); const [cron, setCron] = useState("0 9 * * MON-FRI"); - const [cronMode, setCronMode] = useState("5-part"); + const [cronDialect, setCronDialect] = useState("unix-vixie"); const [rrule, setRrule] = useState("FREQ=WEEKLY;COUNT=12;BYDAY=MO,WE,FR"); const [occurrenceCount, setOccurrenceCount] = useState(12); const [businessDate, setBusinessDate] = useState(() => @@ -113,6 +126,15 @@ export function Workbench() { const [description, setDescription] = useState(""); const [location, setLocation] = useState(""); const [eventDuration, setEventDuration] = useState("PT1H"); + const [meetingPeople, setMeetingPeople] = useState( + "Berlin|Europe/Berlin|09:00|17:00\nNew York|America/New_York|09:00|17:00", + ); + const [meetingDays, setMeetingDays] = useState(7); + const [meetingDuration, setMeetingDuration] = useState(60); + const [meetingStep, setMeetingStep] = useState(30); + const [icsSource, setIcsSource] = useState( + "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Example//EN\r\nBEGIN:VEVENT\r\nUID:example@local\r\nDTSTART;TZID=Europe/Berlin:20260901T090000\r\nRRULE:FREQ=DAILY;COUNT=3\r\nSUMMARY:Example recurrence\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ); const zoneList = useMemo( () => @@ -326,6 +348,91 @@ export function Workbench() { ); + const meetingWorkspace = ( + <> +