From 06645dac056b117df1fd31955286726b9a7da1ef Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Sun, 3 May 2026 14:40:02 +0800 Subject: [PATCH 1/9] licensing: replace LICENSE files with canonical Apache-2.0 text The four LICENSE files (repo root and the three SDKs) carried real Apache-2.0, but the text had been edited: a leading blank line, the APPENDIX section removed, and a hardcoded "Copyright 2026 Oddbit" spliced in place of the placeholder copyright notice. pana matches LICENSE files against the SPDX corpus; once the deltas exceed its threshold it returns "No license was recognized", which cost the Dart SDK the full 10 points on pub.dev's OSI-approved-license check (visible at 150/160 on shrtnr 1.0.1). Replaced all four files with the canonical text from https://www.apache.org/licenses/LICENSE-2.0.txt (sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30). Copyright and attribution already live in each SDK's NOTICE file, so they don't need to be embedded in LICENSE. npm and PyPI don't run pana; the change to those LICENSE files is parity hygiene, not a packaging fix, so no version bump is needed there. The Dart SDK gets a 1.0.2 patch in a follow-up commit so pub.dev re-scores the package. --- LICENSE | 19 +++++++++++++++---- sdk/dart/LICENSE | 19 +++++++++++++++---- sdk/python/LICENSE | 19 +++++++++++++++---- sdk/typescript/LICENSE | 19 +++++++++++++++---- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/LICENSE b/LICENSE index 8fc4ecb..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/dart/LICENSE b/sdk/dart/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/dart/LICENSE +++ b/sdk/dart/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/python/LICENSE b/sdk/python/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/python/LICENSE +++ b/sdk/python/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/typescript/LICENSE b/sdk/typescript/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/typescript/LICENSE +++ b/sdk/typescript/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From f72e3e2b83dbde753f6323243010f06b18f03fad Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Sun, 3 May 2026 14:40:14 +0800 Subject: [PATCH 2/9] Release pub 1.0.2: re-publish so pub.dev re-scores the canonical LICENSE Patch bump only. Picks up the LICENSE normalization from the previous commit so pana recognizes Apache-2.0 and restores the missing 10 points on the OSI-approved-license check. No public surface or dependency changes; spec hash is unchanged. --- sdk/dart/CHANGELOG.md | 4 ++++ sdk/dart/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/dart/CHANGELOG.md b/sdk/dart/CHANGELOG.md index 2822b51..c32fc78 100644 --- a/sdk/dart/CHANGELOG.md +++ b/sdk/dart/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.2 (2026-05-03) + +- Replace `LICENSE` with the canonical Apache-2.0 text from apache.org so pana recognizes the license. + ## 1.0.1 (2026-04-30) Packaging and documentation only. No public surface changes. diff --git a/sdk/dart/pubspec.yaml b/sdk/dart/pubspec.yaml index 829a5e5..2fbba23 100644 --- a/sdk/dart/pubspec.yaml +++ b/sdk/dart/pubspec.yaml @@ -1,6 +1,6 @@ name: shrtnr description: Dart client for the shrtnr URL shortener API. Create short links, manage custom slugs, and read click analytics. -version: 1.0.1 +version: 1.0.2 homepage: https://oddb.it/shrtnr-website-pub repository: https://github.com/oddbit/shrtnr issue_tracker: https://github.com/oddbit/shrtnr/issues From 0199b3c9d087c74b76ca387221bdc60fe321da89 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Thu, 7 May 2026 12:38:52 +0800 Subject: [PATCH 3/9] ui: format delta percentages with locale thousands separators The Delta component rendered raw integers (e.g. "+1400%"), which is awkward once a delta exceeds three digits. Pipe the page's lang through KpiCard and through every Delta call site (dashboard KPIs, links list, bundles list) so the percentage runs through fmtNumber() and renders with the locale's thousands grouping ("+1,400%" in en). Test added: links-page asserts a 4-digit delta surfaces as "+1,400%" and never as the bare "+1400%". --- src/__tests__/page/links-page.test.ts | 23 +++++++++++++++++++++++ src/components/delta.tsx | 6 ++++-- src/components/kpi-card.tsx | 4 +++- src/pages/bundles.tsx | 2 +- src/pages/dashboard.tsx | 4 ++++ src/pages/links.tsx | 2 +- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/__tests__/page/links-page.test.ts b/src/__tests__/page/links-page.test.ts index 290da13..4fa310b 100644 --- a/src/__tests__/page/links-page.test.ts +++ b/src/__tests__/page/links-page.test.ts @@ -133,6 +133,29 @@ describe("Links listing page", () => { expect(html).toMatch(/]*class="[^"]*col-date[^"]*"[^>]*>[\s\S]*?class="delta /); }); + it("delta pct of 4+ digits uses locale thousands separators", async () => { + const link = await LinkRepository.create(env.DB, { + url: "https://example.com", + slug: "abc", + }); + const now = Math.floor(Date.now() / 1000); + // 1 click in the previous 30d window, 15 clicks in the current → pct = 1400 + await env.DB.prepare("INSERT INTO clicks (slug, clicked_at) VALUES (?, ?)") + .bind(link.slugs[0].slug, now - 40 * 86400) + .run(); + for (let i = 0; i < 15; i++) { + await env.DB.prepare("INSERT INTO clicks (slug, clicked_at) VALUES (?, ?)") + .bind(link.slugs[0].slug, now - 60 - i) + .run(); + } + + const res = await SELF.fetch(req("/_/admin/links")); + const html = await res.text(); + // en (default lang) groups with comma. The bare "+1400%" form must not appear. + expect(html).toMatch(/class="delta-label">\+1,400%\+1400% { for (let i = 0; i < 30; i++) { await LinkRepository.create(env.DB, { diff --git a/src/components/delta.tsx b/src/components/delta.tsx index 3e7909d..db5153e 100644 --- a/src/components/delta.tsx +++ b/src/components/delta.tsx @@ -2,20 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 import type { FC } from "hono/jsx"; +import { fmtNumber } from "../i18n/format"; type DeltaProps = { pct: number; + lang: string; id?: string; }; -export const Delta: FC = ({ pct, id }) => { +export const Delta: FC = ({ pct, lang, id }) => { const dir = pct > 0 ? "up" : pct < 0 ? "down" : "flat"; const icon = dir === "up" ? "trending_up" : dir === "down" ? "trending_down" : "trending_flat"; const sign = pct > 0 ? "+" : ""; return ( {icon} - {sign}{pct}% + {sign}{fmtNumber(pct, lang)}% ); }; diff --git a/src/components/kpi-card.tsx b/src/components/kpi-card.tsx index 613178a..68b431c 100644 --- a/src/components/kpi-card.tsx +++ b/src/components/kpi-card.tsx @@ -13,6 +13,7 @@ type KpiCardProps = { valueId?: string; deltaPct?: number | null; deltaId?: string; + lang: string; hint?: string; sparkline?: number[]; span?: 1 | 2 | 3; @@ -26,6 +27,7 @@ export const KpiCard: FC = ({ valueId, deltaPct, deltaId, + lang, hint, sparkline, span = 1, @@ -38,7 +40,7 @@ export const KpiCard: FC = ({ {icon && {icon}} {label} - {deltaPct !== undefined && deltaPct !== null && } + {deltaPct !== undefined && deltaPct !== null && }
{value}
{hint &&
{hint}
} diff --git a/src/pages/bundles.tsx b/src/pages/bundles.tsx index 0dd9567..c505473 100644 --- a/src/pages/bundles.tsx +++ b/src/pages/bundles.tsx @@ -76,7 +76,7 @@ export const BundlesPage: FC = ({ bundles, t, lang, filter, range }) => { {b.archived_at && {t("bundles.archived")}} {b.delta_pct !== undefined && ( - + )} diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx index bdbec70..0f49d35 100644 --- a/src/pages/dashboard.tsx +++ b/src/pages/dashboard.tsx @@ -87,6 +87,7 @@ export const DashboardPage: FC = ({ stats, t, lang, range }) => { valueId="dash-total-links" deltaPct={d.new_links_delta} deltaId="dash-links-delta" + lang={lang} sparkline={d.timeline_links} /> = ({ stats, t, lang, range }) => { valueId="dash-clicked-links" deltaPct={d.clicked_links_delta} deltaId="dash-clicked-links-delta" + lang={lang} sparkline={d.timeline_clicked_links} /> = ({ stats, t, lang, range }) => { valueId="dash-total-clicks" deltaPct={d.total_clicks_delta} deltaId="dash-clicks-delta" + lang={lang} sparkline={d.timeline} /> = ({ stats, t, lang, range }) => { valueId="dash-clicks-per-day" deltaPct={d.clicks_per_day_delta} deltaId="dash-clicks-per-day-delta" + lang={lang} sparkline={d.timeline} /> diff --git a/src/pages/links.tsx b/src/pages/links.tsx index 9208e70..0f05a1b 100644 --- a/src/pages/links.tsx +++ b/src/pages/links.tsx @@ -247,7 +247,7 @@ export const LinksPage: FC = ({ {formatDate(link.created_at, lang)} {typeof link.delta_pct === "number" && link.total_clicks > 0 && ( - + )} From 531a62bbeae5e0c9845f7baa4876ec17c838b721 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 7 May 2026 05:04:33 +0000 Subject: [PATCH 4/9] test: pin lang=en cookie in delta thousands-separators test The grouping assertion previously relied on the app's default lang being "en". Set the cookie explicitly so the test stays deterministic if the default locale ever changes. --- src/__tests__/page/links-page.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/__tests__/page/links-page.test.ts b/src/__tests__/page/links-page.test.ts index 4fa310b..4382e8e 100644 --- a/src/__tests__/page/links-page.test.ts +++ b/src/__tests__/page/links-page.test.ts @@ -6,8 +6,8 @@ import { SELF, env } from "cloudflare:test"; import { LinkRepository, SlugRepository } from "../../db"; import { applyMigrations, resetData } from "../setup"; -function req(path: string): Request { - return new Request(`https://shrtnr.test${path}`); +function req(path: string, init?: RequestInit): Request { + return new Request(`https://shrtnr.test${path}`, init); } beforeAll(applyMigrations); @@ -149,9 +149,10 @@ describe("Links listing page", () => { .run(); } - const res = await SELF.fetch(req("/_/admin/links")); + // Pin lang=en so the comma-grouping assertion is deterministic regardless of + // future default-locale changes. + const res = await SELF.fetch(req("/_/admin/links", { headers: { Cookie: "lang=en" } })); const html = await res.text(); - // en (default lang) groups with comma. The bare "+1400%" form must not appear. expect(html).toMatch(/class="delta-label">\+1,400%\+1400% Date: Thu, 7 May 2026 05:21:02 +0000 Subject: [PATCH 5/9] fix(delta): normalize -0 so flat delta does not render as -0% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Math.round can return -0 for values like ((99.9 - 100) / 100) * 100, and Intl.NumberFormat renders -0 as "-0" (or U+2212-prefixed in sv). With the old comparison logic, dir lands at "flat" while the label shows "-0%". Add `pct + 0` to coerce -0 → +0 before computing direction, sign, and formatted output. New unit tests cover the regression along with the existing positive/negative/locale-grouping cases. --- src/__tests__/unit/delta.test.ts | 41 ++++++++++++++++++++++++++++++++ src/components/delta.tsx | 11 +++++---- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/unit/delta.test.ts diff --git a/src/__tests__/unit/delta.test.ts b/src/__tests__/unit/delta.test.ts new file mode 100644 index 0000000..93ffe13 --- /dev/null +++ b/src/__tests__/unit/delta.test.ts @@ -0,0 +1,41 @@ +// Copyright 2026 Oddbit (https://oddbit.id) +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; +import { jsx } from "hono/jsx"; +import { Delta } from "../../components/delta"; + +function render(props: { pct: number; lang: string }): string { + return String(jsx(Delta, props)); +} + +describe("Delta component", () => { + it("renders +1,400% for an en delta of 1400", () => { + const out = render({ pct: 1400, lang: "en" }); + expect(out).toContain("+1,400%"); + expect(out).toContain("delta up"); + expect(out).toContain("trending_up"); + }); + + it("normalizes -0 to 0 so the flat delta does not render -0%", () => { + // Math.round(((99.9 - 100) / 100) * 100) === -0, which Intl.NumberFormat + // would otherwise render as "-0". + const out = render({ pct: -0, lang: "en" }); + expect(out).toContain(">0%<"); + expect(out).not.toContain("-0%"); + expect(out).not.toContain("−0%"); + expect(out).toContain("delta flat"); + expect(out).toContain("trending_flat"); + }); + + it("groups thousands using the active locale", () => { + expect(render({ pct: 1400, lang: "id" })).toContain("+1.400%"); + }); + + it("renders a negative delta with a minus and the down direction", () => { + const out = render({ pct: -25, lang: "en" }); + expect(out).toContain("-25%"); + expect(out).toContain("delta down"); + expect(out).toContain("trending_down"); + }); +}); diff --git a/src/components/delta.tsx b/src/components/delta.tsx index db5153e..bf4866f 100644 --- a/src/components/delta.tsx +++ b/src/components/delta.tsx @@ -11,13 +11,16 @@ type DeltaProps = { }; export const Delta: FC = ({ pct, lang, id }) => { - const dir = pct > 0 ? "up" : pct < 0 ? "down" : "flat"; + // Normalize -0 to 0 so Intl.NumberFormat does not render a confusing "-0%" + // when dir is "flat". + const safePct = pct + 0; + const dir = safePct > 0 ? "up" : safePct < 0 ? "down" : "flat"; const icon = dir === "up" ? "trending_up" : dir === "down" ? "trending_down" : "trending_flat"; - const sign = pct > 0 ? "+" : ""; + const sign = safePct > 0 ? "+" : ""; return ( - + {icon} - {sign}{fmtNumber(pct, lang)}% + {sign}{fmtNumber(safePct, lang)}% ); }; From 61904ad74a64ecf53052cdf51e136cf65b8faf8c Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Thu, 7 May 2026 13:32:27 +0800 Subject: [PATCH 6/9] fix(delta): use Object.is(-0) instead of pct + 0 to normalize negative zero The previous `pct + 0` form produced the same result but read like an accidental coercion. The explicit `Object.is(pct, -0) ? 0 : pct` makes the intent obvious to future readers. --- src/components/delta.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/delta.tsx b/src/components/delta.tsx index bf4866f..5674885 100644 --- a/src/components/delta.tsx +++ b/src/components/delta.tsx @@ -13,7 +13,7 @@ type DeltaProps = { export const Delta: FC = ({ pct, lang, id }) => { // Normalize -0 to 0 so Intl.NumberFormat does not render a confusing "-0%" // when dir is "flat". - const safePct = pct + 0; + const safePct = Object.is(pct, -0) ? 0 : pct; const dir = safePct > 0 ? "up" : safePct < 0 ? "down" : "flat"; const icon = dir === "up" ? "trending_up" : dir === "down" ? "trending_down" : "trending_flat"; const sign = safePct > 0 ? "+" : ""; From fdca6536603a17f1f5739b0c26ba25f6bfee53c7 Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Thu, 7 May 2026 13:32:32 +0800 Subject: [PATCH 7/9] test(links-page): hoist prepared statement out of click-insert loop The 1400% delta test prepared the same INSERT inside the loop. Prepare once and rebind per iteration. --- src/__tests__/page/links-page.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/__tests__/page/links-page.test.ts b/src/__tests__/page/links-page.test.ts index 4382e8e..143a621 100644 --- a/src/__tests__/page/links-page.test.ts +++ b/src/__tests__/page/links-page.test.ts @@ -140,13 +140,10 @@ describe("Links listing page", () => { }); const now = Math.floor(Date.now() / 1000); // 1 click in the previous 30d window, 15 clicks in the current → pct = 1400 - await env.DB.prepare("INSERT INTO clicks (slug, clicked_at) VALUES (?, ?)") - .bind(link.slugs[0].slug, now - 40 * 86400) - .run(); + const insertClick = env.DB.prepare("INSERT INTO clicks (slug, clicked_at) VALUES (?, ?)"); + await insertClick.bind(link.slugs[0].slug, now - 40 * 86400).run(); for (let i = 0; i < 15; i++) { - await env.DB.prepare("INSERT INTO clicks (slug, clicked_at) VALUES (?, ?)") - .bind(link.slugs[0].slug, now - 60 - i) - .run(); + await insertClick.bind(link.slugs[0].slug, now - 60 - i).run(); } // Pin lang=en so the comma-grouping assertion is deterministic regardless of From deb66c984eefcaaf2e1bb9f86282bc72489d731a Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Thu, 7 May 2026 13:45:02 +0800 Subject: [PATCH 8/9] licensing: restore canonical Apache-2.0 text in LICENSE files Reverts the text changes from 06645da. Restores the verbatim apache.org wording ("submitted to Licensor", "received by Licensor", "excluding those notices") and the APPENDIX block at the end of the file. --- LICENSE | 19 ++++--------------- sdk/dart/LICENSE | 19 ++++--------------- sdk/python/LICENSE | 19 ++++--------------- sdk/typescript/LICENSE | 19 ++++--------------- 4 files changed, 16 insertions(+), 60 deletions(-) diff --git a/LICENSE b/LICENSE index d645695..8fc4ecb 100644 --- a/LICENSE +++ b/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner + submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and + on behalf of whom a Contribution has been received by the Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not + within such NOTICE file, excluding any notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Oddbit (https://oddbit.id) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/dart/LICENSE b/sdk/dart/LICENSE index d645695..8fc4ecb 100644 --- a/sdk/dart/LICENSE +++ b/sdk/dart/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner + submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and + on behalf of whom a Contribution has been received by the Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not + within such NOTICE file, excluding any notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Oddbit (https://oddbit.id) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/python/LICENSE b/sdk/python/LICENSE index d645695..8fc4ecb 100644 --- a/sdk/python/LICENSE +++ b/sdk/python/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner + submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and + on behalf of whom a Contribution has been received by the Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not + within such NOTICE file, excluding any notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Oddbit (https://oddbit.id) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/typescript/LICENSE b/sdk/typescript/LICENSE index d645695..8fc4ecb 100644 --- a/sdk/typescript/LICENSE +++ b/sdk/typescript/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner + submitted to the Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and + on behalf of whom a Contribution has been received by the Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not + within such NOTICE file, excluding any notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,18 +176,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Oddbit (https://oddbit.id) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 508ec18c81228d017e7180ffe486a826f25564be Mon Sep 17 00:00:00 2001 From: Dennis Alund Date: Thu, 7 May 2026 13:45:58 +0800 Subject: [PATCH 9/9] licensing: restore canonical Apache-2.0 text (revert deb66c9) The previous revert was wrong: main carried older non-canonical wording, and 06645da's text was the apache.org canonical form. Verified byte-for-byte against https://www.apache.org/licenses/LICENSE-2.0.txt. --- LICENSE | 19 +++++++++++++++---- sdk/dart/LICENSE | 19 +++++++++++++++---- sdk/python/LICENSE | 19 +++++++++++++++---- sdk/typescript/LICENSE | 19 +++++++++++++++---- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/LICENSE b/LICENSE index 8fc4ecb..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/dart/LICENSE b/sdk/dart/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/dart/LICENSE +++ b/sdk/dart/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/python/LICENSE b/sdk/python/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/python/LICENSE +++ b/sdk/python/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/sdk/typescript/LICENSE b/sdk/typescript/LICENSE index 8fc4ecb..d645695 100644 --- a/sdk/typescript/LICENSE +++ b/sdk/typescript/LICENSE @@ -49,7 +49,7 @@ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner + submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent @@ -61,7 +61,7 @@ designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and + on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of @@ -107,7 +107,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -176,7 +176,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Oddbit (https://oddbit.id) + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.