mindwtr-mcp 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1395 -260
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3049,9 +3049,28 @@ var require_data = __commonJS((exports, module) => {
3049
3049
  var require_utils = __commonJS((exports, module) => {
3050
3050
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3051
3051
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3052
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3052
3053
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3053
3054
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3054
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3055
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3056
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3057
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3058
+ var BYTE_HEX = new Array(256);
3059
+ {
3060
+ const HEX_DIGITS = "0123456789ABCDEF";
3061
+ for (let i = 0;i < 256; i++) {
3062
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3063
+ }
3064
+ }
3065
+ function percentEncodeNonAscii(cp) {
3066
+ if (cp < 2048) {
3067
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3068
+ }
3069
+ if (cp < 65536) {
3070
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3071
+ }
3072
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3073
+ }
3055
3074
  function stringArrayToHexStripped(input) {
3056
3075
  let acc = "";
3057
3076
  let code = 0;
@@ -3076,91 +3095,122 @@ var require_utils = __commonJS((exports, module) => {
3076
3095
  }
3077
3096
  return acc;
3078
3097
  }
3098
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3099
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3100
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3079
3101
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3080
- function consumeIsZone(buffer) {
3081
- buffer.length = 0;
3082
- return true;
3083
- }
3084
- function consumeHextets(buffer, address, output) {
3085
- if (buffer.length) {
3086
- const hex = stringArrayToHexStripped(buffer);
3087
- if (hex !== "") {
3088
- address.push(hex);
3089
- } else {
3090
- output.error = true;
3091
- return false;
3102
+ function isZoneIdentifier(zone) {
3103
+ if (zone.length === 0)
3104
+ return false;
3105
+ for (let i = 0;i < zone.length; i++) {
3106
+ if (isZoneCharacter(zone[i]))
3107
+ continue;
3108
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3109
+ i += 2;
3110
+ continue;
3092
3111
  }
3093
- buffer.length = 0;
3112
+ return false;
3094
3113
  }
3095
3114
  return true;
3096
3115
  }
3097
- function getIPV6(input) {
3098
- let tokenCount = 0;
3099
- const output = { error: false, address: "", zone: "" };
3100
- const address = [];
3101
- const buffer = [];
3102
- let endipv6Encountered = false;
3103
- let endIpv6 = false;
3104
- let consume = consumeHextets;
3105
- for (let i = 0;i < input.length; i++) {
3106
- const cursor = input[i];
3107
- if (cursor === "[" || cursor === "]") {
3108
- continue;
3109
- }
3110
- if (cursor === ":") {
3111
- if (endipv6Encountered === true) {
3112
- endIpv6 = true;
3113
- }
3114
- if (!consume(buffer, address, output)) {
3115
- break;
3116
- }
3117
- if (++tokenCount > 7) {
3118
- output.error = true;
3119
- break;
3120
- }
3121
- if (i > 0 && input[i - 1] === ":") {
3122
- endipv6Encountered = true;
3123
- }
3124
- address.push(":");
3125
- continue;
3126
- } else if (cursor === "%") {
3127
- if (!consume(buffer, address, output)) {
3128
- break;
3116
+ function compressIPv6ZeroRun(hextets) {
3117
+ let bestStart = -1;
3118
+ let bestLength = 0;
3119
+ let runStart = -1;
3120
+ let runLength = 0;
3121
+ for (let i = 0;i < hextets.length; i++) {
3122
+ if (hextets[i] === "0") {
3123
+ if (runStart === -1)
3124
+ runStart = i;
3125
+ runLength++;
3126
+ if (runLength > bestLength) {
3127
+ bestLength = runLength;
3128
+ bestStart = runStart;
3129
3129
  }
3130
- consume = consumeIsZone;
3131
3130
  } else {
3132
- buffer.push(cursor);
3133
- continue;
3131
+ runStart = -1;
3132
+ runLength = 0;
3134
3133
  }
3135
3134
  }
3136
- if (buffer.length) {
3137
- if (consume === consumeIsZone) {
3138
- output.zone = buffer.join("");
3139
- } else if (endIpv6) {
3140
- address.push(buffer.join(""));
3141
- } else {
3142
- address.push(stringArrayToHexStripped(buffer));
3135
+ if (bestLength < 2)
3136
+ return hextets.join(":");
3137
+ const head = hextets.slice(0, bestStart).join(":");
3138
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3139
+ return head + "::" + tail;
3140
+ }
3141
+ function normalizeIPv6Address(input) {
3142
+ const compression = input.indexOf("::");
3143
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1)
3144
+ return;
3145
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3146
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3147
+ if (compression !== -1) {
3148
+ if (left.length === 1 && left[0] === "")
3149
+ left.length = 0;
3150
+ if (right.length === 1 && right[0] === "")
3151
+ right.length = 0;
3152
+ }
3153
+ const parts = left.concat(right);
3154
+ let hextetCount = 0;
3155
+ for (let i = 0;i < parts.length; i++) {
3156
+ const part = parts[i];
3157
+ if (part === "")
3158
+ return;
3159
+ if (part.indexOf(".") !== -1) {
3160
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part))
3161
+ return;
3162
+ hextetCount += 2;
3163
+ continue;
3143
3164
  }
3165
+ if (!isHextet(part))
3166
+ return;
3167
+ parts[i] = parseInt(part, 16).toString(16);
3168
+ hextetCount++;
3144
3169
  }
3145
- output.address = address.join("");
3146
- return output;
3170
+ if (compression === -1) {
3171
+ if (hextetCount !== 8)
3172
+ return;
3173
+ return compressIPv6ZeroRun(parts);
3174
+ }
3175
+ if (hextetCount >= 8)
3176
+ return;
3177
+ const expanded = parts.slice(0, left.length);
3178
+ for (let i = hextetCount;i < 8; i++)
3179
+ expanded.push("0");
3180
+ for (let i = left.length;i < parts.length; i++)
3181
+ expanded.push(parts[i]);
3182
+ return compressIPv6ZeroRun(expanded);
3147
3183
  }
3148
3184
  function normalizeIPv6(host) {
3149
- if (findToken(host, ":") < 2) {
3150
- return { host, isIPV6: false };
3151
- }
3152
- const ipv62 = getIPV6(host);
3153
- if (!ipv62.error) {
3154
- let newHost = ipv62.address;
3155
- let escapedHost = ipv62.address;
3156
- if (ipv62.zone) {
3157
- newHost += "%" + ipv62.zone;
3158
- escapedHost += "%25" + ipv62.zone;
3159
- }
3160
- return { host: newHost, isIPV6: true, escapedHost };
3161
- } else {
3162
- return { host, isIPV6: false };
3163
- }
3185
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3186
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3187
+ if (hasBracket && !bracketed)
3188
+ return { host, isIPV6: false, error: true };
3189
+ let input = bracketed ? host.slice(1, -1) : host;
3190
+ if (bracketed && isIPvFuture(input)) {
3191
+ input = input.toLowerCase();
3192
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3193
+ }
3194
+ if (findToken(input, ":") < 2) {
3195
+ return { host, isIPV6: false, error: bracketed };
3196
+ }
3197
+ let zoneIdentifier = "";
3198
+ const zoneSeparator = input.indexOf("%");
3199
+ if (zoneSeparator !== -1) {
3200
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3201
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3202
+ if (!isZoneIdentifier(zoneIdentifier))
3203
+ return { host, isIPV6: false, error: true };
3204
+ input = input.slice(0, zoneSeparator);
3205
+ }
3206
+ const address = normalizeIPv6Address(input);
3207
+ if (address === undefined)
3208
+ return { host, isIPV6: false, error: true };
3209
+ return {
3210
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3211
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3212
+ isIPV6: true
3213
+ };
3164
3214
  }
3165
3215
  function findToken(str, token) {
3166
3216
  let ind = 0;
@@ -3280,7 +3330,8 @@ var require_utils = __commonJS((exports, module) => {
3280
3330
  function normalizePathEncoding(input) {
3281
3331
  let output = "";
3282
3332
  for (let i = 0;i < input.length; i++) {
3283
- if (input[i] === "%" && i + 2 < input.length) {
3333
+ const ch = input[i];
3334
+ if (ch === "%" && i + 2 < input.length) {
3284
3335
  const hex = input.slice(i + 1, i + 3);
3285
3336
  if (isHexPair(hex)) {
3286
3337
  const normalizedHex = hex.toUpperCase();
@@ -3294,10 +3345,152 @@ var require_utils = __commonJS((exports, module) => {
3294
3345
  continue;
3295
3346
  }
3296
3347
  }
3297
- if (isPathCharacter(input[i])) {
3298
- output += input[i];
3348
+ if (isPathCharacter(ch)) {
3349
+ output += ch;
3350
+ } else {
3351
+ const code = input.charCodeAt(i);
3352
+ if (code < 128) {
3353
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3354
+ } else if (code < 55296 || code > 57343) {
3355
+ output += percentEncodeNonAscii(code);
3356
+ } else if (code <= 56319 && i + 1 < input.length) {
3357
+ const low = input.charCodeAt(i + 1);
3358
+ if (low >= 56320 && low <= 57343) {
3359
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3360
+ i++;
3361
+ } else {
3362
+ output += percentEncodeNonAscii(65533);
3363
+ }
3364
+ } else {
3365
+ output += percentEncodeNonAscii(65533);
3366
+ }
3367
+ }
3368
+ }
3369
+ return output;
3370
+ }
3371
+ function serializePathEncoding(input, pathNoScheme = false) {
3372
+ let output = "";
3373
+ let firstSegment = pathNoScheme && input[0] !== "/";
3374
+ for (let i = 0;i < input.length; i++) {
3375
+ const ch = input[i];
3376
+ if (ch === "%" && i + 2 < input.length) {
3377
+ const hex = input.slice(i + 1, i + 3);
3378
+ if (isHexPair(hex)) {
3379
+ output += "%" + hex.toUpperCase();
3380
+ i += 2;
3381
+ continue;
3382
+ }
3383
+ }
3384
+ if (ch === "/") {
3385
+ firstSegment = false;
3386
+ }
3387
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3388
+ output += ch;
3389
+ } else {
3390
+ const code = input.charCodeAt(i);
3391
+ if (code < 128) {
3392
+ output += BYTE_HEX[code];
3393
+ } else if (code < 55296 || code > 57343) {
3394
+ output += percentEncodeNonAscii(code);
3395
+ } else if (code <= 56319 && i + 1 < input.length) {
3396
+ const low = input.charCodeAt(i + 1);
3397
+ if (low >= 56320 && low <= 57343) {
3398
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3399
+ i++;
3400
+ } else {
3401
+ output += percentEncodeNonAscii(65533);
3402
+ }
3403
+ } else {
3404
+ output += percentEncodeNonAscii(65533);
3405
+ }
3406
+ }
3407
+ }
3408
+ return output;
3409
+ }
3410
+ function encodeComponent(input, isAllowed) {
3411
+ let output = "";
3412
+ for (let i = 0;i < input.length; i++) {
3413
+ const ch = input[i];
3414
+ if (ch === "%" && i + 2 < input.length) {
3415
+ const hex = input.slice(i + 1, i + 3);
3416
+ if (isHexPair(hex)) {
3417
+ output += "%" + hex.toUpperCase();
3418
+ i += 2;
3419
+ continue;
3420
+ }
3421
+ }
3422
+ if (isAllowed(ch)) {
3423
+ output += ch;
3424
+ } else {
3425
+ const code = input.charCodeAt(i);
3426
+ if (code < 128) {
3427
+ output += BYTE_HEX[code];
3428
+ } else if (code < 55296 || code > 57343) {
3429
+ output += percentEncodeNonAscii(code);
3430
+ } else if (code <= 56319 && i + 1 < input.length) {
3431
+ const low = input.charCodeAt(i + 1);
3432
+ if (low >= 56320 && low <= 57343) {
3433
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3434
+ i++;
3435
+ } else {
3436
+ output += percentEncodeNonAscii(65533);
3437
+ }
3438
+ } else {
3439
+ output += percentEncodeNonAscii(65533);
3440
+ }
3441
+ }
3442
+ }
3443
+ return output;
3444
+ }
3445
+ function encodeUserinfo(input) {
3446
+ return encodeComponent(input, isUserinfoCharacter);
3447
+ }
3448
+ function encodeQuery(input) {
3449
+ return encodeComponent(input, isQueryFragmentCharacter);
3450
+ }
3451
+ function encodeFragment(input) {
3452
+ return encodeComponent(input, isQueryFragmentCharacter);
3453
+ }
3454
+ function isEscapeSafe(cp) {
3455
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
3456
+ }
3457
+ function normalizeQueryFragmentEncoding(input) {
3458
+ let output = "";
3459
+ for (let i = 0;i < input.length; i++) {
3460
+ const ch = input[i];
3461
+ if (ch === "%" && i + 2 < input.length) {
3462
+ const hex = input.slice(i + 1, i + 3);
3463
+ if (isHexPair(hex)) {
3464
+ const normalizedHex = hex.toUpperCase();
3465
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3466
+ if (isUnreserved(decoded)) {
3467
+ output += decoded;
3468
+ } else {
3469
+ output += "%" + normalizedHex;
3470
+ }
3471
+ i += 2;
3472
+ continue;
3473
+ }
3474
+ }
3475
+ if (isQueryFragmentCharacter(ch)) {
3476
+ output += ch;
3299
3477
  } else {
3300
- output += escape(input[i]);
3478
+ const code = input.charCodeAt(i);
3479
+ if (code < 128) {
3480
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3481
+ } else if (code < 55296 || code > 57343) {
3482
+ output += percentEncodeNonAscii(code);
3483
+ } else if (code <= 56319 && i + 1 < input.length) {
3484
+ const low = input.charCodeAt(i + 1);
3485
+ if (low >= 56320 && low <= 57343) {
3486
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3487
+ i++;
3488
+ } else {
3489
+ output += percentEncodeNonAscii(65533);
3490
+ }
3491
+ } else {
3492
+ output += percentEncodeNonAscii(65533);
3493
+ }
3301
3494
  }
3302
3495
  }
3303
3496
  return output;
@@ -3320,14 +3513,18 @@ var require_utils = __commonJS((exports, module) => {
3320
3513
  function recomposeAuthority(component) {
3321
3514
  const uriTokens = [];
3322
3515
  if (component.userinfo !== undefined) {
3323
- uriTokens.push(component.userinfo);
3516
+ uriTokens.push(encodeUserinfo(component.userinfo));
3324
3517
  uriTokens.push("@");
3325
3518
  }
3326
3519
  if (component.host !== undefined) {
3327
- let host = unescape(component.host);
3520
+ let host = component.host;
3328
3521
  if (!isIPv4(host)) {
3329
- const ipV6res = normalizeIPv6(host);
3330
- if (ipV6res.isIPV6 === true) {
3522
+ let ipV6res = normalizeIPv6(host);
3523
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3524
+ host = normalizePercentEncoding(host, true);
3525
+ ipV6res = normalizeIPv6(host);
3526
+ }
3527
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3331
3528
  host = `[${ipV6res.escapedHost}]`;
3332
3529
  } else {
3333
3530
  host = reescapeHostDelimiters(host, false);
@@ -3336,8 +3533,12 @@ var require_utils = __commonJS((exports, module) => {
3336
3533
  uriTokens.push(host);
3337
3534
  }
3338
3535
  if (typeof component.port === "number" || typeof component.port === "string") {
3536
+ const port = String(component.port);
3537
+ if (!isPort(port)) {
3538
+ throw new TypeError("URI port is malformed.");
3539
+ }
3339
3540
  uriTokens.push(":");
3340
- uriTokens.push(String(component.port));
3541
+ uriTokens.push(port);
3341
3542
  }
3342
3543
  return uriTokens.length ? uriTokens.join("") : undefined;
3343
3544
  }
@@ -3347,6 +3548,11 @@ var require_utils = __commonJS((exports, module) => {
3347
3548
  reescapeHostDelimiters,
3348
3549
  normalizePercentEncoding,
3349
3550
  normalizePathEncoding,
3551
+ serializePathEncoding,
3552
+ normalizeQueryFragmentEncoding,
3553
+ encodeUserinfo,
3554
+ encodeQuery,
3555
+ encodeFragment,
3350
3556
  escapePreservingEscapes,
3351
3557
  removeDotSegments,
3352
3558
  isIPv4,
@@ -3359,7 +3565,7 @@ var require_utils = __commonJS((exports, module) => {
3359
3565
  // ../../node_modules/fast-uri/lib/schemes.js
3360
3566
  var require_schemes = __commonJS((exports, module) => {
3361
3567
  var { isUUID } = require_utils();
3362
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3568
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3363
3569
  var supportedSchemeNames = [
3364
3570
  "http",
3365
3571
  "https",
@@ -3414,9 +3620,10 @@ var require_schemes = __commonJS((exports, module) => {
3414
3620
  wsComponent.secure = undefined;
3415
3621
  }
3416
3622
  if (wsComponent.resourceName) {
3417
- const [path, query] = wsComponent.resourceName.split("?");
3623
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3624
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3418
3625
  wsComponent.path = path && path !== "/" ? path : undefined;
3419
- wsComponent.query = query;
3626
+ wsComponent.query = queryIndex === -1 ? undefined : wsComponent.resourceName.slice(queryIndex + 1);
3420
3627
  wsComponent.resourceName = undefined;
3421
3628
  }
3422
3629
  wsComponent.fragment = undefined;
@@ -3428,7 +3635,7 @@ var require_schemes = __commonJS((exports, module) => {
3428
3635
  return urnComponent;
3429
3636
  }
3430
3637
  const matches = urnComponent.path.match(URN_REG);
3431
- if (matches) {
3638
+ if (matches && matches[0] === urnComponent.path) {
3432
3639
  const scheme = options.scheme || urnComponent.scheme || "urn";
3433
3640
  urnComponent.nid = matches[1].toLowerCase();
3434
3641
  urnComponent.nss = matches[2];
@@ -3532,8 +3739,17 @@ var require_schemes = __commonJS((exports, module) => {
3532
3739
 
3533
3740
  // ../../node_modules/fast-uri/index.js
3534
3741
  var require_fast_uri = __commonJS((exports, module) => {
3535
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3742
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3536
3743
  var { SCHEMES, getSchemeHandler } = require_schemes();
3744
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
3745
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
3746
+ function decodeValidScheme(scheme) {
3747
+ const decodedScheme = unescape(String(scheme));
3748
+ if (!VALID_SCHEME.test(decodedScheme)) {
3749
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
3750
+ }
3751
+ return decodedScheme;
3752
+ }
3537
3753
  function normalize(uri, options) {
3538
3754
  if (typeof uri === "string") {
3539
3755
  uri = normalizeString(uri, options);
@@ -3544,12 +3760,34 @@ var require_fast_uri = __commonJS((exports, module) => {
3544
3760
  }
3545
3761
  function resolve(baseURI, relativeURI, options) {
3546
3762
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3547
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3548
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3549
- if (baseMalformed || relativeMalformed) {
3763
+ const {
3764
+ parsed: baseParsed,
3765
+ malformedAuthorityOrPort: baseMalformed,
3766
+ malformedPercentEncoding: baseMalformedPercentEncoding,
3767
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
3768
+ malformedHost: baseMalformedHost,
3769
+ malformedScheme: baseMalformedScheme
3770
+ } = parseWithStatus(baseURI, schemelessOptions);
3771
+ const {
3772
+ parsed: relativeParsed,
3773
+ malformedAuthorityOrPort: relativeMalformed,
3774
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
3775
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
3776
+ malformedHost: relativeMalformedHost,
3777
+ malformedScheme: relativeMalformedScheme
3778
+ } = parseWithStatus(relativeURI, schemelessOptions);
3779
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3550
3780
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3551
3781
  }
3552
3782
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
3783
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
3784
+ const resolvedHost = resolved.host;
3785
+ const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
3786
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
3787
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
3788
+ if (resolved.error && !encodedASCIIHost) {
3789
+ throw new Error(resolved.error);
3790
+ }
3553
3791
  schemelessOptions.skipEscape = true;
3554
3792
  return serialize(resolved, schemelessOptions);
3555
3793
  }
@@ -3609,7 +3847,7 @@ var require_fast_uri = __commonJS((exports, module) => {
3609
3847
  function equal(uriA, uriB, options) {
3610
3848
  const normalizedA = normalizeComparableURI(uriA, options);
3611
3849
  const normalizedB = normalizeComparableURI(uriB, options);
3612
- return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3850
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB;
3613
3851
  }
3614
3852
  function serialize(cmpts, opts) {
3615
3853
  const component = {
@@ -3630,20 +3868,23 @@ var require_fast_uri = __commonJS((exports, module) => {
3630
3868
  };
3631
3869
  const options = Object.assign({}, opts);
3632
3870
  const uriTokens = [];
3871
+ if (component.scheme) {
3872
+ component.scheme = decodeValidScheme(component.scheme);
3873
+ }
3633
3874
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3634
3875
  if (schemeHandler && schemeHandler.serialize)
3635
3876
  schemeHandler.serialize(component, options);
3877
+ const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
3878
+ const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
3636
3879
  if (component.path !== undefined) {
3637
3880
  if (!options.skipEscape) {
3638
- component.path = escapePreservingEscapes(component.path);
3639
- if (component.scheme !== undefined) {
3640
- component.path = component.path.split("%3A").join(":");
3641
- }
3881
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3642
3882
  } else {
3643
3883
  component.path = normalizePercentEncoding(component.path);
3644
3884
  }
3645
3885
  }
3646
3886
  if (options.reference !== "suffix" && component.scheme) {
3887
+ component.scheme = decodeValidScheme(component.scheme);
3647
3888
  uriTokens.push(component.scheme, ":");
3648
3889
  }
3649
3890
  const authority = recomposeAuthority(component);
@@ -3661,16 +3902,19 @@ var require_fast_uri = __commonJS((exports, module) => {
3661
3902
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3662
3903
  s = removeDotSegments(s);
3663
3904
  }
3905
+ if (pathNoScheme) {
3906
+ s = serializePathEncoding(s, true);
3907
+ }
3664
3908
  if (authority === undefined && s[0] === "/" && s[1] === "/") {
3665
3909
  s = "/%2F" + s.slice(2);
3666
3910
  }
3667
3911
  uriTokens.push(s);
3668
3912
  }
3669
3913
  if (component.query !== undefined) {
3670
- uriTokens.push("?", component.query);
3914
+ uriTokens.push("?", encodeQuery(component.query));
3671
3915
  }
3672
3916
  if (component.fragment !== undefined) {
3673
- uriTokens.push("#", component.fragment);
3917
+ uriTokens.push("#", encodeFragment(component.fragment));
3674
3918
  }
3675
3919
  return uriTokens.join("");
3676
3920
  }
@@ -3686,6 +3930,36 @@ var require_fast_uri = __commonJS((exports, module) => {
3686
3930
  }
3687
3931
  return;
3688
3932
  }
3933
+ function hasMalformedPercentEncoding(component) {
3934
+ if (component === undefined)
3935
+ return false;
3936
+ let percent = component.indexOf("%");
3937
+ while (percent !== -1) {
3938
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
3939
+ return true;
3940
+ }
3941
+ percent = component.indexOf("%", percent + 3);
3942
+ }
3943
+ return false;
3944
+ }
3945
+ function isIPLiteral(host) {
3946
+ return host[0] === "[" && host[host.length - 1] === "]";
3947
+ }
3948
+ function hasMalformedComponentPercentEncoding(matches) {
3949
+ const host = matches[4];
3950
+ return hasMalformedPercentEncoding(matches[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
3951
+ }
3952
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
3953
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3954
+ try {
3955
+ parsed.host = new URL("http://" + parsed.host).hostname;
3956
+ } catch (e) {
3957
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3958
+ return true;
3959
+ }
3960
+ }
3961
+ return false;
3962
+ }
3689
3963
  function parseWithStatus(uri, opts) {
3690
3964
  const options = Object.assign({}, opts);
3691
3965
  const parsed = {
@@ -3698,6 +3972,11 @@ var require_fast_uri = __commonJS((exports, module) => {
3698
3972
  fragment: undefined
3699
3973
  };
3700
3974
  let malformedAuthorityOrPort = false;
3975
+ let malformedPercentEncoding = false;
3976
+ let malformedSchemeSpecific = false;
3977
+ let malformedHost = false;
3978
+ let malformedIPLiteral = false;
3979
+ let malformedScheme = false;
3701
3980
  let isIP = false;
3702
3981
  if (options.reference === "suffix") {
3703
3982
  if (options.scheme) {
@@ -3734,6 +4013,19 @@ var require_fast_uri = __commonJS((exports, module) => {
3734
4013
  parsed.path = matches[6] || "";
3735
4014
  parsed.query = matches[7];
3736
4015
  parsed.fragment = matches[8];
4016
+ if (parsed.scheme !== undefined) {
4017
+ const decodedScheme = unescape(parsed.scheme);
4018
+ if (VALID_SCHEME.test(decodedScheme)) {
4019
+ parsed.scheme = decodedScheme.toLowerCase();
4020
+ } else {
4021
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4022
+ malformedScheme = true;
4023
+ }
4024
+ }
4025
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4026
+ if (malformedPercentEncoding) {
4027
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4028
+ }
3737
4029
  if (isNaN(parsed.port)) {
3738
4030
  parsed.port = matches[5];
3739
4031
  }
@@ -3745,9 +4037,16 @@ var require_fast_uri = __commonJS((exports, module) => {
3745
4037
  if (parsed.host) {
3746
4038
  const ipv4result = isIPv4(parsed.host);
3747
4039
  if (ipv4result === false) {
4040
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
4041
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
3748
4042
  const ipv6result = normalizeIPv6(parsed.host);
3749
- parsed.host = ipv6result.host.toLowerCase();
3750
- isIP = ipv6result.isIPV6;
4043
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4044
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4045
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4046
+ if (malformedIPLiteral) {
4047
+ parsed.error = parsed.error || "URI host is malformed.";
4048
+ malformedAuthorityOrPort = true;
4049
+ }
3751
4050
  } else {
3752
4051
  isIP = true;
3753
4052
  }
@@ -3765,42 +4064,36 @@ var require_fast_uri = __commonJS((exports, module) => {
3765
4064
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3766
4065
  }
3767
4066
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3768
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3769
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3770
- try {
3771
- parsed.host = new URL("http://" + parsed.host).hostname;
3772
- } catch (e) {
3773
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3774
- }
3775
- }
4067
+ if (!malformedIPLiteral) {
4068
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
3776
4069
  }
3777
4070
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3778
4071
  if (uri.indexOf("%") !== -1) {
3779
- if (parsed.scheme !== undefined) {
3780
- parsed.scheme = unescape(parsed.scheme);
3781
- }
3782
- if (parsed.host !== undefined) {
3783
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
4072
+ if (parsed.host !== undefined && !malformedIPLiteral) {
4073
+ const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4074
+ parsed.host = reescapeHostDelimiters(host, isIP);
3784
4075
  }
3785
4076
  }
3786
4077
  if (parsed.path) {
3787
4078
  parsed.path = normalizePathEncoding(parsed.path);
3788
4079
  }
4080
+ if (parsed.query) {
4081
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
4082
+ }
3789
4083
  if (parsed.fragment) {
3790
- try {
3791
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3792
- } catch {
3793
- parsed.error = parsed.error || "URI malformed";
3794
- }
4084
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3795
4085
  }
3796
4086
  }
3797
4087
  if (schemeHandler && schemeHandler.parse) {
3798
4088
  schemeHandler.parse(parsed, options);
4089
+ if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
4090
+ malformedSchemeSpecific = true;
4091
+ }
3799
4092
  }
3800
4093
  } else {
3801
4094
  parsed.error = parsed.error || "URI can not be parsed.";
3802
4095
  }
3803
- return { parsed, malformedAuthorityOrPort };
4096
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3804
4097
  }
3805
4098
  function parse6(uri, opts) {
3806
4099
  return parseWithStatus(uri, opts).parsed;
@@ -3809,20 +4102,28 @@ var require_fast_uri = __commonJS((exports, module) => {
3809
4102
  return normalizeStringWithStatus(uri, opts).normalized;
3810
4103
  }
3811
4104
  function normalizeStringWithStatus(uri, opts) {
3812
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4105
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
3813
4106
  return {
3814
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3815
- malformedAuthorityOrPort
4107
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4108
+ malformedAuthorityOrPort,
4109
+ malformedPercentEncoding,
4110
+ malformedSchemeSpecific,
4111
+ malformedHost,
4112
+ malformedScheme
3816
4113
  };
3817
4114
  }
3818
4115
  function normalizeComparableURI(uri, opts) {
3819
- if (typeof uri === "string") {
3820
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
3821
- return malformedAuthorityOrPort ? undefined : normalized;
4116
+ if (typeof uri !== "string" && typeof uri !== "object") {
4117
+ return;
3822
4118
  }
3823
- if (typeof uri === "object") {
3824
- return serialize(uri, opts);
4119
+ let value;
4120
+ try {
4121
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4122
+ } catch {
4123
+ return;
3825
4124
  }
4125
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4126
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized;
3826
4127
  }
3827
4128
  var fastUri = {
3828
4129
  SCHEMES,
@@ -14730,6 +15031,8 @@ var init_zh_Hans = __esm(() => {
14730
15031
  "attachments.fileTooLarge": "文件过大,无法上传。",
14731
15032
  "attachments.fileNotReadable": "无法读取此文件,因此未添加附件。请将文件移动到其他文件夹后重试。",
14732
15033
  "attachments.linkToFile": "链接到文件…",
15034
+ "attachments.linkedFileElsewhere": "此链接指向另一台设备上的文件:{{path}}。请在那台设备上打开,或改为直接添加该文件作为附件。",
15035
+ "attachments.openLinkFailed": "无法打开此链接。",
14733
15036
  "attachments.invalidFileType": "不支持的文件类型。",
14734
15037
  "attachments.invalidLink": "请输入有效的链接。",
14735
15038
  "attachments.photoUnavailableTitle": "图片选择不可用",
@@ -15470,6 +15773,8 @@ var init_zh_Hans = __esm(() => {
15470
15773
  "settings.syncEncryptionUnlock": "输入密码短语",
15471
15774
  "settings.syncEncryptionDecline": "暂不",
15472
15775
  "settings.syncEncryptionPausedDesc": "在您输入密码短语之前,本设备的自动同步将保持暂停。",
15776
+ "settings.syncEncryptionLockedRecheckHint": "如果该同步位置已不再存放加密文件,请点按“立即同步”,本设备会重新检查该位置并继续同步。",
15777
+ "settings.syncEncryptionNoEncryptedRemote": "该同步位置已没有加密文件,因此本设备的加密现已关闭。如需加密该位置,请重新开启。",
15473
15778
  "settings.syncEncryptionRemoteEncrypted": "此同步位置已加密。请输入其同步密码短语以继续同步。",
15474
15779
  "settings.syncEncryptionRemotePlaintext": "同步已停止:此同步位置不再加密。请在此设备上关闭同步加密,或在该同步位置重新启用加密。",
15475
15780
  "settings.syncEncryptionRemotePlaintextDesc": "另一台设备在此同步位置关闭了加密。此设备上的内容没有被更改或降级。若要以明文继续同步,请在此设备上关闭同步加密;否则请在该同步位置重新启用加密。",
@@ -16430,6 +16735,12 @@ App Store 已提供更新,是否立即打开应用页面?`,
16430
16735
  "settings.syncMobile.accountStatus": "账户状态",
16431
16736
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "请在 Dropbox OAuth 设置里添加以下精确回调地址。",
16432
16737
  "settings.syncMobile.backgroundSync": "后台同步",
16738
+ "settings.syncMobile.backgroundSyncInterval": "后台同步间隔",
16739
+ "settings.syncMobile.backgroundSyncIntervalDescription": "应用关闭期间,系统最多按此间隔运行同步任务。“关闭”表示手机仅在应用打开时、离开应用时以及编辑后不久同步。",
16740
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "每 15 分钟",
16741
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "每 6 小时",
16742
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "每小时",
16743
+ "settings.syncMobile.backgroundSyncIntervalOff": "关闭",
16433
16744
  "settings.syncMobile.clearPendingAttachmentDeletes": "清除待处理的附件删除?",
16434
16745
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "这台设备上的 CloudKit 已被限制。请检查屏幕使用时间、设备管理或 iCloud 限制后再试。",
16435
16746
  "settings.syncMobile.connectedToDropbox": "已连接 Dropbox。",
@@ -16517,6 +16828,9 @@ App Store 已提供更新,是否立即打开应用页面?`,
16517
16828
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 端点可访问。",
16518
16829
  "settings.persistentCaptureLabel": "通知栏快速收集",
16519
16830
  "settings.persistentCaptureDesc": "保留常驻通知,随时随地(包括锁屏)快速收集。",
16831
+ "settings.exactAlarmsLabel": "提醒可能会延迟",
16832
+ "settings.exactAlarmsDesc": "Android 未允许 Mindwtr 设置精确闹钟,提醒可能会晚一分钟才响起。",
16833
+ "settings.exactAlarmsAllow": "允许",
16520
16834
  "settings.appSearchLabel": "在系统搜索中显示",
16521
16835
  "settings.appSearchDesc": "允许 Android 系统搜索按标题查找你的活动任务、项目和领域。数据不会离开此设备。",
16522
16836
  "captureNotification.title": "快速收集",
@@ -16851,6 +17165,8 @@ App Store 已提供更新,是否立即打开应用页面?`,
16851
17165
  "settings.gettingStartedContentContinueDesc": "完成此处的设置后,你仍可添加引导式“快速上手”项目和示例收集箱项目。",
16852
17166
  "settings.syncSetupGuideTitle": "数据与同步设置指南",
16853
17167
  "settings.syncSetupGuideDesc": "Dropbox、iCloud、WebDAV、文件同步和恢复的设置说明。",
17168
+ "settings.syncEncryptionGuideTitle": "同步加密指南",
17169
+ "settings.syncEncryptionGuideDesc": "它保护什么、哪些服务器支持它,以及口令如何工作。",
16854
17170
  "settings.importSetupGuideTitle": "导入设置指南",
16855
17171
  "settings.importSetupGuideDesc": "支持 Todoist、TickTick、DGT GTD、OmniFocus、Mindwtr CSV、Apple 提醒事项和备份导入路径。",
16856
17172
  "settings.backupDiagnostics.newerVersion": "此备份由较新版本的 Mindwtr({{version}})创建。",
@@ -16873,7 +17189,7 @@ App Store 已提供更新,是否立即打开应用页面?`,
16873
17189
  "settings.importDiagnostics.unmappedDate": "{{count}} 个日期值无法转换,已省略。",
16874
17190
  "settings.importDiagnostics.unmappedStatus": "{{count}} 个状态值无法转换,已使用安全默认值。",
16875
17191
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} 条不支持的重复规则已保留为备注。",
16876
- "settings.syncRemoteBusy": "另一台兼容的 Mindwtr 设备正在更新此同步位置。请等待其完成,然后再次同步。",
17192
+ "settings.syncRemoteBusy": "另一台 Mindwtr 设备暂时占用了此同步位置。同步将自动重试。",
16877
17193
  "settings.syncRemoteCleanupDeferred": "同步操作已完成。Mindwtr 无法移除临时同步锁,但该锁会自动过期。无需重试。",
16878
17194
  "settings.syncAttachmentWriteDeferred": "部分附件更改未能完成。请恢复缺失的本地文件或移除受影响的附件,然后再次同步。",
16879
17195
  "settings.syncFileAttachmentTooLarge": "Mindwtr 已保留本地附件。File Sync 只能同步小于 100 MB 的附件。请换用较小的文件或移除该附件,然后再次同步。",
@@ -17223,6 +17539,8 @@ var init_zh_Hant = __esm(() => {
17223
17539
  "attachments.fileTooLarge": "文件過大,無法上傳。",
17224
17540
  "attachments.fileNotReadable": "無法讀取此文件,因此未加入附件。請將文件移至其他資料夾後再試一次。",
17225
17541
  "attachments.linkToFile": "連結到文件…",
17542
+ "attachments.linkedFileElsewhere": "此連結指向另一台裝置上的檔案:{{path}}。請在該裝置上開啟,或改為直接加入該檔案作為附件。",
17543
+ "attachments.openLinkFailed": "無法開啟此連結。",
17226
17544
  "attachments.invalidFileType": "不支持的文件類型。",
17227
17545
  "attachments.invalidLink": "請輸入有效的鏈接。",
17228
17546
  "attachments.photoUnavailableTitle": "圖片選擇不可用",
@@ -17963,6 +18281,8 @@ var init_zh_Hant = __esm(() => {
17963
18281
  "settings.syncEncryptionUnlock": "輸入密碼短語",
17964
18282
  "settings.syncEncryptionDecline": "暫時不要",
17965
18283
  "settings.syncEncryptionPausedDesc": "在您輸入密碼短語之前,本裝置的自動同步會保持暫停。",
18284
+ "settings.syncEncryptionLockedRecheckHint": "如果該同步位置已不再存放加密檔案,請點按「立即同步」,本裝置會重新檢查該位置並繼續同步。",
18285
+ "settings.syncEncryptionNoEncryptedRemote": "該同步位置已沒有加密檔案,因此本裝置的加密現已關閉。如需加密該位置,請重新開啟。",
17966
18286
  "settings.syncEncryptionRemoteEncrypted": "此同步位置已加密。請輸入其同步密碼短語以繼續同步。",
17967
18287
  "settings.syncEncryptionRemotePlaintext": "同步已停止:此同步位置不再加密。請在此裝置上關閉同步加密,或在該同步位置重新啟用加密。",
17968
18288
  "settings.syncEncryptionRemotePlaintextDesc": "另一台裝置在此同步位置關閉了加密。此裝置上的內容沒有被變更或降級。若要以純文字繼續同步,請在此裝置上關閉同步加密;否則請在該同步位置重新啟用加密。",
@@ -18923,6 +19243,12 @@ App Store 已提供更新,是否立即打開應用頁面?`,
18923
19243
  "settings.syncMobile.accountStatus": "賬戶狀態",
18924
19244
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "請在 Dropbox OAuth 設置裡添加以下精確回調地址。",
18925
19245
  "settings.syncMobile.backgroundSync": "後臺同步",
19246
+ "settings.syncMobile.backgroundSyncInterval": "後臺同步間隔",
19247
+ "settings.syncMobile.backgroundSyncIntervalDescription": "應用程式關閉期間,系統最多按此間隔執行同步工作。「關閉」表示手機僅在應用程式開啟時、離開應用程式時以及編輯後不久同步。",
19248
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "每 15 分鐘",
19249
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "每 6 小時",
19250
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "每小時",
19251
+ "settings.syncMobile.backgroundSyncIntervalOff": "關閉",
18926
19252
  "settings.syncMobile.clearPendingAttachmentDeletes": "清除待處理的附件刪除?",
18927
19253
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "這臺設備上的 CloudKit 已被限制。請檢查屏幕使用時間、設備管理或 iCloud 限制後再試。",
18928
19254
  "settings.syncMobile.connectedToDropbox": "已連接 Dropbox。",
@@ -19010,6 +19336,9 @@ App Store 已提供更新,是否立即打開應用頁面?`,
19010
19336
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 端點可訪問。",
19011
19337
  "settings.persistentCaptureLabel": "通知列快速收集",
19012
19338
  "settings.persistentCaptureDesc": "保留常駐通知,隨時隨地(包括鎖定畫面)快速收集。",
19339
+ "settings.exactAlarmsLabel": "提醒可能會延遲",
19340
+ "settings.exactAlarmsDesc": "Android 未允許 Mindwtr 設定精確鬧鐘,提醒可能會晚一分鐘才響起。",
19341
+ "settings.exactAlarmsAllow": "允許",
19013
19342
  "settings.appSearchLabel": "在系統搜尋中顯示",
19014
19343
  "settings.appSearchDesc": "允許 Android 系統搜尋依標題找到你的進行中任務、專案與領域。資料不會離開此裝置。",
19015
19344
  "captureNotification.title": "快速收集",
@@ -19344,6 +19673,8 @@ App Store 已提供更新,是否立即打開應用頁面?`,
19344
19673
  "settings.gettingStartedContentContinueDesc": "完成這裡的設定後,你仍可加入引導式「快速上手」專案和範例收集箱項目。",
19345
19674
  "settings.syncSetupGuideTitle": "資料與同步設定指南",
19346
19675
  "settings.syncSetupGuideDesc": "Dropbox、iCloud、WebDAV、檔案同步和還原的設定說明。",
19676
+ "settings.syncEncryptionGuideTitle": "同步加密指南",
19677
+ "settings.syncEncryptionGuideDesc": "它保護什麼、哪些伺服器支援它,以及密語如何運作。",
19347
19678
  "settings.importSetupGuideTitle": "匯入設定指南",
19348
19679
  "settings.importSetupGuideDesc": "支援 Todoist、TickTick、DGT GTD、OmniFocus、Mindwtr CSV、Apple 提醒事項和備份匯入路徑。",
19349
19680
  "settings.backupDiagnostics.newerVersion": "此備份由較新版本的 Mindwtr({{version}})建立。",
@@ -19366,7 +19697,7 @@ App Store 已提供更新,是否立即打開應用頁面?`,
19366
19697
  "settings.importDiagnostics.unmappedDate": "{{count}} 個日期值無法轉換,已省略。",
19367
19698
  "settings.importDiagnostics.unmappedStatus": "{{count}} 個狀態值無法轉換,已使用安全預設值。",
19368
19699
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} 條不支援的重複規則已保留為備註。",
19369
- "settings.syncRemoteBusy": "另一台相容的 Mindwtr 裝置正在更新此同步位置。請等待其完成,然後再次同步。",
19700
+ "settings.syncRemoteBusy": "另一台 Mindwtr 裝置暫時佔用了此同步位置。同步將自動重試。",
19370
19701
  "settings.syncRemoteCleanupDeferred": "同步作業已完成。Mindwtr 無法移除暫時同步鎖,但該鎖會自動失效。無需重試。",
19371
19702
  "settings.syncAttachmentWriteDeferred": "部分附件變更未能完成。請還原缺少的本機檔案或移除受影響的附件,然後再次同步。",
19372
19703
  "settings.syncFileAttachmentTooLarge": "Mindwtr 已保留本機附件。File Sync 只能同步小於 100 MB 的附件。請改用較小的檔案或移除該附件,然後再次同步。",
@@ -23876,7 +24207,7 @@ var init_de = __esm(() => {
23876
24207
  "quickAdd.help": 'Schnell hinzufügen unterstützt /start:<when>, /due:<when>, /review:<when>, /note:<text>, /link:<url>, /energy:<level>, /priority:<level>, /next, /area:<name> oder !Area, @context, #tag, +Project, %Person (oder %"Full Name").',
23877
24208
  "quickAdd.example": "Beispiel: Mama anrufen /due:tomorrow @phone",
23878
24209
  "quickAdd.inlineHint": "Tipp: Mama anrufen /due:tomorrow 5pm @phone #family",
23879
- "quickAdd.syntaxHelp": "Hilfe zur Schnell-hinzufügen-Syntax",
24210
+ "quickAdd.syntaxHelp": "Hilfe zur Schnell-Hinzufügen-Syntax",
23880
24211
  "quickAdd.placeholder": "Eine Aufgabe hinzufügen … benutzen Sie @context +Project #tag",
23881
24212
  "quickAdd.inputLabel": "Schnelle Eingabe",
23882
24213
  "quickAdd.inputHint": "Geben Sie eine Aufgabe ein und drücken Sie Eingabe zum Speichern.",
@@ -23917,7 +24248,7 @@ var init_de = __esm(() => {
23917
24248
  "quickAdd.audioSavingSpeechToText": "Aufnahme wird gespeichert und Sprache in Text umgewandelt.",
23918
24249
  "quickAdd.audioNoteTitle": "Sprachnotiz",
23919
24250
  "quickAdd.audioPermissionTitle": "Zugriff auf Mikrofon ist notwendig",
23920
- "quickAdd.audioPermissionBody": "Mikrofon-Zugriff erlauben, um Sprachnotizen aufzunehmen.",
24251
+ "quickAdd.audioPermissionBody": "Mikrofonzugriff erlauben, um Sprachnotizen aufzunehmen.",
23921
24252
  "quickAdd.audioErrorTitle": "Aufnahme fehlgeschlagen",
23922
24253
  "quickAdd.audioErrorBody": "Wir konnten nichts aufnehmen. Bitte versuchen Sie es nochmals.",
23923
24254
  "quickAdd.invalidDateCommand": "Ungültiger Datumsbefehl",
@@ -23942,7 +24273,7 @@ var init_de = __esm(() => {
23942
24273
  "keybindings.section.taskList": "Aufgabenliste",
23943
24274
  "keybindings.section.quickAddSyntax": "Schnelleingabe-Syntax",
23944
24275
  "keybindings.section.global": "Allgemein",
23945
- "keybindings.openHelp": "Taststaturkürzel anzeigen",
24276
+ "keybindings.openHelp": "Tastaturkürzel anzeigen",
23946
24277
  "keybindings.openSettings": "Einstellungen öffnen",
23947
24278
  "keybindings.toggleFullscreen": "Vollbild umschalten",
23948
24279
  "keybindings.switchArea": "Bereich wechseln",
@@ -23987,7 +24318,7 @@ var init_de = __esm(() => {
23987
24318
  "list.todo": "Zu tun",
23988
24319
  "list.inProgress": "In Arbeit",
23989
24320
  "list.next": "Nächste Aktionen",
23990
- "list.someday": "Irgendwann/Veilleicht",
24321
+ "list.someday": "Irgendwann/Vielleicht",
23991
24322
  "list.reference": "Referenz",
23992
24323
  "list.waiting": "Abwarten",
23993
24324
  "list.done": "Abgeschlossen",
@@ -24005,7 +24336,7 @@ var init_de = __esm(() => {
24005
24336
  "list.densityComfortable": "Komfortabel",
24006
24337
  "list.densityCompact": "Kompakt",
24007
24338
  "list.densityCondensed": "Verdichtet",
24008
- "reference.empty": "Noch keine Referenz-Einträge.",
24339
+ "reference.empty": "Noch keine Referenzeinträge.",
24009
24340
  "status.inbox": "Posteingang",
24010
24341
  "status.todo": "Zu tun",
24011
24342
  "status.next": "Nächstes",
@@ -24043,7 +24374,7 @@ var init_de = __esm(() => {
24043
24374
  "taskEdit.duplicateDoneBody": "Es wurde eine neue Kopie im Posteingang erstellt.",
24044
24375
  "taskEdit.aiClarify": "Klären mit KI",
24045
24376
  "taskEdit.aiBreakdown": "KI-Zusammenfassung",
24046
- "taskEdit.itemNamePlaceholder": "Elementen-Name",
24377
+ "taskEdit.itemNamePlaceholder": "Elementname",
24047
24378
  "taskEdit.titleLabel": "Titel",
24048
24379
  "taskEdit.editorLayoutHelpLabel": "Editor-Layout-Hilfe",
24049
24380
  "taskEdit.editorLayoutHelpText": "Du kannst in Einstellungen -> GTD -> Aufgaben-Editor-Layout anpassen, welche Felder hier angezeigt werden.",
@@ -24076,7 +24407,7 @@ var init_de = __esm(() => {
24076
24407
  "markdown.collapse": "Zusammenklappen",
24077
24408
  "markdown.toolbar.heading": "Titel einfügen",
24078
24409
  "markdown.toolbar.bold": "Fett",
24079
- "markdown.toolbar.italic": "Schrägschrift",
24410
+ "markdown.toolbar.italic": "Kursiv",
24080
24411
  "markdown.toolbar.strikethrough": "Durchgestrichen",
24081
24412
  "markdown.toolbar.bulletList": "Punkteliste",
24082
24413
  "markdown.toolbar.horizontalRule": "Horizontale Linie",
@@ -24093,7 +24424,7 @@ var init_de = __esm(() => {
24093
24424
  "attachments.downloadConflict": "Dieser Anhang wurde während des Downloads geändert. Die lokale Datei wurde beibehalten. Synchronisiere erneut, um den Konflikt zu lösen.",
24094
24425
  "attachments.unrecoverable": "Dieser Anhang ist im synchronisierten Speicher nicht mehr verfügbar. Der ungültige Verweis wurde entfernt.",
24095
24426
  "attachments.remove": "Entfernen",
24096
- "attachments.transferProgress": "Fortschritt der Anhangsübertragung",
24427
+ "attachments.transferProgress": "Fortschritt der Übertragung von Anhängen",
24097
24428
  "attachments.linkPlaceholder": "https://beispiel.com",
24098
24429
  "attachments.linkInputHint": 'Tipp: eine URL einfügen, oder "Titel | URL" benutzen.',
24099
24430
  "attachments.attachObsidianNote": "Obsidian-Notiz anhängen",
@@ -24103,12 +24434,12 @@ var init_de = __esm(() => {
24103
24434
  "attachments.fileTooLarge": "Die Datei ist zu groß für das Hochladen.",
24104
24435
  "attachments.fileNotReadable": "Diese Datei konnte nicht gelesen werden und wurde daher nicht angehängt. Verschiebe sie in einen anderen Ordner und versuche es erneut.",
24105
24436
  "attachments.linkToFile": "Mit Datei verknüpfen…",
24106
- "attachments.invalidFileType": "Nicht unterstützter Dateientyp.",
24437
+ "attachments.invalidFileType": "Nicht unterstützter Dateityp.",
24107
24438
  "attachments.invalidLink": "Bitte eine gültige URL angeben.",
24108
- "attachments.photoUnavailableTitle": "Foto-Auswahl ist nicht verfügbar",
24439
+ "attachments.photoUnavailableTitle": "Fotoauswahl ist nicht verfügbar",
24109
24440
  "attachments.photoUnavailableBody": "Die App neu erstellen, um Fotoanhänge zu ermöglichen.",
24110
24441
  "taskEdit.locationLabel": "Standort",
24111
- "taskEdit.locationPlaceholder": "z.B. Büro",
24442
+ "taskEdit.locationPlaceholder": "z. B. Büro",
24112
24443
  "taskEdit.projectLabel": "Projekt",
24113
24444
  "taskEdit.noProjectOption": "Kein Projekt",
24114
24445
  "taskEdit.sectionLabel": "Abschnitt",
@@ -24118,7 +24449,7 @@ var init_de = __esm(() => {
24118
24449
  "taskEdit.moreOptions": "Mehr Optionen",
24119
24450
  "taskEdit.hideOptions": "Optionen verstecken",
24120
24451
  "taskEdit.startDateLabel": "Anfangsdatum",
24121
- "taskEdit.dueDateLabel": "Fälligekeitsdatum",
24452
+ "taskEdit.dueDateLabel": "Fälligkeitsdatum",
24122
24453
  "taskEdit.reviewDateLabel": "Revisions-Datum",
24123
24454
  "taskEdit.dateOnly": "Nur Datum",
24124
24455
  "taskEdit.startModeLabel": "Startmodus",
@@ -24195,16 +24526,16 @@ var init_de = __esm(() => {
24195
24526
  "recurrence.ordinal.second": "Zweiten",
24196
24527
  "recurrence.ordinal.third": "Dritten",
24197
24528
  "recurrence.ordinal.fourth": "Vierten",
24198
- "recurrence.ordinal.last": "Letzen",
24529
+ "recurrence.ordinal.last": "Letzten",
24199
24530
  "recurrence.monthlyOnDay": "Der gleiche Tag jeden Monats",
24200
24531
  "recurrence.monthlyOnLastWeekday": "Letzter {weekday}",
24201
- "recurrence.strategyLabel": "Stratgisch",
24532
+ "recurrence.strategyLabel": "Strategisch",
24202
24533
  "recurrence.strategyStrict": "Strikt",
24203
24534
  "recurrence.strategyFluid": "Fliessend",
24204
- "recurrence.strategyStrictDesc": "Fällig am geplanten Datum (z.B. Rechnungen)",
24205
- "recurrence.strategyFluidDesc": "Fällig nach Abschluss (z.B. Wäsche)",
24206
- "recurrence.afterCompletion": "Nach Abschluß wiederholen",
24207
- "recurrence.afterCompletionShort": "Nach Abschluß",
24535
+ "recurrence.strategyStrictDesc": "Fällig am geplanten Datum (z. B. Rechnungen)",
24536
+ "recurrence.strategyFluidDesc": "Fällig nach Abschluss (z. B. Wäsche)",
24537
+ "recurrence.afterCompletion": "Nach Abschluss wiederholen",
24538
+ "recurrence.afterCompletionShort": "Nach Abschluss",
24208
24539
  "recurrence.showFutureInCalendar": "Künftige Wiederholungen im Kalender anzeigen",
24209
24540
  "recurrence.showFutureInCalendarHint": "Nur Planungsvorschau; die nächste echte Aufgabe wird erst erstellt, wenn diese abgeschlossen wird.",
24210
24541
  "inbox.title": "Posteingang",
@@ -24218,7 +24549,7 @@ var init_de = __esm(() => {
24218
24549
  "inbox.refineHint": "Präzisiere den Titel und die Details, bevor Sie entscheiden, was als Nächstes getan werden soll.",
24219
24550
  "inbox.refineNext": "Nächstes",
24220
24551
  "inbox.refineDelete": "Löschen",
24221
- "inbox.isActionable": "Ist dies ausführbar?",
24552
+ "inbox.isActionable": "Ist diese ausführbar?",
24222
24553
  "inbox.actionableHint": "Können Sie eine physische Handlung ausführen?",
24223
24554
  "inbox.yes": "Ja",
24224
24555
  "inbox.no": "Nein",
@@ -24239,7 +24570,7 @@ var init_de = __esm(() => {
24239
24570
  "inbox.addContextPlaceholder": "Neuen Kontext hinzufügen ...",
24240
24571
  "inbox.waitingQuestion": "Auf wen oder was warten Sie?",
24241
24572
  "inbox.waitingHint": "Fügen Sie eine Notiz hinzu, um sich zu erinnern, auf was Sie warten",
24242
- "inbox.waitingPlaceholder": "Z.B. Auf Jonathan's Revision warten ...",
24573
+ "inbox.waitingPlaceholder": "Z. B. Auf Jonathan's Revision warten ...",
24243
24574
  "inbox.assignProjectQuestion": "Zu einem Projekt hinzufügen? (Optional)",
24244
24575
  "inbox.noProject": "Kein Projekt",
24245
24576
  "inbox.skip": "Überspringen",
@@ -24251,7 +24582,7 @@ var init_de = __esm(() => {
24251
24582
  "next.noTasks": "Keine Aufgaben in Zu Tun. Fügen Sie sie in den Posteingang ein und verarbeiten Sie diese zuerst.",
24252
24583
  "next.noContext": "Keine Nächsten Aktionen mit",
24253
24584
  "next.warningCount": "Elemente in Nächste Aktionen",
24254
- "next.warningHint": "Überlegen Sie sich, sich auf weniger Projekte zu konzentieren. GTD empfiehlt, höchstens 10-15 Elemente in Nächste Aktionen zu lassen für eine besser Übersicht.",
24585
+ "next.warningHint": "Überlegen Sie sich, sich auf weniger Projekte zu konzentrieren. GTD empfiehlt, höchstens 10-15 Elemente in Nächste Aktionen zu lassen für eine besser Übersicht.",
24255
24586
  "contexts.title": "Kontexte",
24256
24587
  "contexts.filter": "Aufgaben nach Kontexten filtern",
24257
24588
  "filters.label": "Filter",
@@ -24281,7 +24612,7 @@ var init_de = __esm(() => {
24281
24612
  "filters.noMatch": " Diesem Filter entsprechen keine Aufgaben.",
24282
24613
  "contexts.all": "Alle Kontexte",
24283
24614
  "contexts.none": "Keine Kontexte",
24284
- "contexts.noContexts": "Keine Kontexte gefunden. Fügen Sie Ihren Aufgaben Kontexte wie z.B. @home, @work, @computer hinzu.",
24615
+ "contexts.noContexts": "Keine Kontexte gefunden. Fügen Sie Ihren Aufgaben Kontexte wie z. B. @home, @work, @computer hinzu.",
24285
24616
  "contexts.noTasks": "Keine aktiven Aufgaben für diesen Kontext",
24286
24617
  "board.title": "Tafelansicht",
24287
24618
  "board.next": "Nächste Aktionen",
@@ -24289,11 +24620,11 @@ var init_de = __esm(() => {
24289
24620
  "board.inProgress": "In Arbeit",
24290
24621
  "board.done": "Erledigt",
24291
24622
  "board.noTasks": "Keine Aufgaben",
24292
- "board.hint": "Halten zum Verschieben • Nach links swipen zum Löschen",
24623
+ "board.hint": "Halten zum Verschieben • Zum Löschen nach links wischen",
24293
24624
  "board.dragTask": "Aufgabe verschieben",
24294
24625
  "board.delete": "Löschen",
24295
24626
  "calendar.title": "Kalender",
24296
- "calendar.addTask": "Neue Aufgabe hinzfügen ...",
24627
+ "calendar.addTask": "Neue Aufgabe hinzufügen ...",
24297
24628
  "calendar.schedulePlaceholder": "Aufgaben suchen zum Einplanen ...",
24298
24629
  "calendar.scheduleResults": "Zeitplan",
24299
24630
  "calendar.scheduleAction": "Zeitplan",
@@ -24351,10 +24682,10 @@ var init_de = __esm(() => {
24351
24682
  "project.notes": "Projektnotizen",
24352
24683
  "projects.notesPlaceholder": "Kontexte, Pläne oder Referenzen zu diesem Projekt hinzufügen ...",
24353
24684
  "projects.sectionNotes": "Abschnitts-Notizen",
24354
- "projects.sectionNotesPlaceholder": "Notizen zu diesem Abschnitt hinzfügen ...",
24355
- "projects.reviewAt": "Datum revisieren",
24685
+ "projects.sectionNotesPlaceholder": "Notizen zu diesem Abschnitt hinzufügen ...",
24686
+ "projects.reviewAt": "Revisionsdatum",
24356
24687
  "projects.areaLabel": "Bereich",
24357
- "projects.areaPlaceholder": "Z.B. Arbeit",
24688
+ "projects.areaPlaceholder": "Z. B. Arbeit",
24358
24689
  "projects.sectionsLabel": "Abschnitte",
24359
24690
  "projects.addSection": "Abschnitt hinzufügen",
24360
24691
  "projects.sectionPlaceholder": "Titel für den Abschnitt",
@@ -24407,7 +24738,7 @@ var init_de = __esm(() => {
24407
24738
  "dailyReview.todayStep": "Heute & Kalender",
24408
24739
  "dailyReview.todayDesc": "Überprüfen, was heute ansteht und welche Verpflichtungen im Kalender stehen.",
24409
24740
  "dailyReview.focusStep": "Heutiger Fokus",
24410
- "dailyReview.focusDesc": "Wählen Sie bis zu 3 Fokus-Aufgaben für heute aus.",
24741
+ "dailyReview.focusDesc": "Optional: Aufgaben markieren, die heute im Fokus sichtbar bleiben sollen.",
24411
24742
  "dailyReview.inboxStep": "Posteingang verarbeiten",
24412
24743
  "dailyReview.inboxDesc": "Neue Eingaben klären in der Rechte-Liste.",
24413
24744
  "dailyReview.waitingStep": "Warten auf",
@@ -24430,9 +24761,9 @@ var init_de = __esm(() => {
24430
24761
  "review.calendarStep": "Kalender-Revision",
24431
24762
  "review.calendarStepDesc": "Revidieren Sie Ihren Kalender für die nächste 7 Tage.",
24432
24763
  "review.past14": "Letzte 14 Tage",
24433
- "review.past14Desc": "Revidieren Sie Ihren Kalender für die letzen zwei Wochen. Haben Sie irgednetwas vermisst? Benötigen irgendwelche abgeschlossen Termine eine nachfolgende Aktion?",
24764
+ "review.past14Desc": "Revidieren Sie Ihren Kalender für die letzten zwei Wochen. Haben Sie irgendetwas vermisst? Benötigen irgendwelche abgeschlossen Termine eine nachfolgende Aktion?",
24434
24765
  "review.upcoming14": "Nächste 7 Tage",
24435
- "review.upcoming14Desc": "Schauen Sie auf die nächste Woche. Auf wasmüssen Sie sich vorbereiten? Erfassen Sie alle neuen nächsten Aktionen.",
24766
+ "review.upcoming14Desc": "Schauen Sie auf die nächste Woche. Auf was müssen Sie sich vorbereiten? Erfassen Sie alle neuen nächsten Aktionen.",
24436
24767
  "review.waitingStep": "Warten auf",
24437
24768
  "review.waitingStepDesc": "Verfolgen Sie die delegierten Aufgaben.",
24438
24769
  "review.waitingHint": "Revidieren Sie diese Elemente. Haben Sie erhalten, auf was Sie warten? Müssen Sie eine Erinnerung absenden?",
@@ -24442,8 +24773,8 @@ var init_de = __esm(() => {
24442
24773
  "review.staleStep": "Liegengebliebene Aufgaben",
24443
24774
  "review.staleStepDesc": "Keine Aktivität in letzter Zeit. Aktualisieren, abschließen oder loslassen.",
24444
24775
  "review.staleDaysInactive": "{{days}} Tage inaktiv",
24445
- "review.advanceWeek": "In 1 Woche prüfen",
24446
- "review.advanceWeekDone": "Nächste Durchsicht in 1 Woche",
24776
+ "review.advanceWeek": "In einer Woche prüfen",
24777
+ "review.advanceWeekDone": "Nächste Durchsicht in einer Woche",
24447
24778
  "review.aiStep": "KI-Einblick",
24448
24779
  "review.aiStepDesc": "Veraltete Elemente hervorheben und Bereinigungsvorschläge anzeigen.",
24449
24780
  "review.aiTitle": "KI-Revision",
@@ -24451,14 +24782,14 @@ var init_de = __esm(() => {
24451
24782
  "review.aiRunning": "Analysieren ...",
24452
24783
  "review.aiEmpty": "Keine veralteten Elemente gefunden.",
24453
24784
  "review.aiApply": "Ausgewählte anwenden",
24454
- "review.aiAction.someday": "Nach Irgedendwann verschieben",
24785
+ "review.aiAction.someday": "Nach Irgendwann verschieben",
24455
24786
  "review.aiAction.archive": "Archiv",
24456
24787
  "review.aiAction.breakdown": "Benötigt eine Aufschlüsselung",
24457
24788
  "review.aiAction.keep": "Behalten",
24458
24789
  "review.notDueYet": "Noch nicht fällig",
24459
24790
  "review.projectsStep": "Projekte revidieren",
24460
24791
  "review.projectsStepDesc": "Sicherstellen, dass jedes aktive Projekt eine nächste Aktion enthält.",
24461
- "review.projectsHint": "Jedes Projekt revidieren. Enthälte es zumindest eine konkrete nächste Aktion? Wenn nicht, Fügen Sie jetzt eine hinzu. Markieren Sie abgeschlossen Projekte als erledigt.",
24792
+ "review.projectsHint": "Jedes Projekt revidieren. Enthält es zumindest eine konkrete nächste Aktion? Wenn nicht, Fügen Sie jetzt eine hinzu. Markieren Sie abgeschlossen Projekte als erledigt.",
24462
24793
  "review.hasNextAction": "Enthält eine nächste Aktion",
24463
24794
  "review.needsAction": "Benötigt eine nächste Aktion",
24464
24795
  "review.noActiveTasks": "Keine aktiven Aufgaben",
@@ -24469,7 +24800,7 @@ var init_de = __esm(() => {
24469
24800
  "review.allDone": "Alles erledigt!",
24470
24801
  "review.allDoneDesc": "Sie sind für bereit für die kommende Woche.",
24471
24802
  "review.complete": "Revision abgeschlossen!",
24472
- "review.completeDesc": "Sie habe Ihren Posteingang abgeklärt, Ihre Listen aktualisiert und sind bereit, Ihre Arbeit zu beginnnen.",
24803
+ "review.completeDesc": "Sie habe Ihren Posteingang abgeklärt, Ihre Listen aktualisiert und sind bereit, Ihre Arbeit zu beginnen.",
24473
24804
  "review.summaryInboxEmpty": "Eingang ist leer",
24474
24805
  "review.summaryInboxCount": "{{count}} Element(e) noch im Eingang",
24475
24806
  "review.summaryProjectsOk": "Jedes aktive Projekt hat eine nächste Aktion",
@@ -24512,10 +24843,10 @@ var init_de = __esm(() => {
24512
24843
  "process.nextStepDesc": "Sollen Sie es tun oder an jemanden delegieren?",
24513
24844
  "process.doIt": "\uD83D\uDCCB Ich werde es selber erledigen",
24514
24845
  "process.delegate": "Delegieren",
24515
- "process.delegateTitle": "Delegierung",
24846
+ "process.delegateTitle": "Delegieren",
24516
24847
  "process.delegateDesc": "Optional: Notieren Sie, wer beteiligt ist, und einen Termin für die Weiterverfolgung.",
24517
24848
  "process.delegateWhoLabel": "Wer? (Optional)",
24518
- "process.delegateWhoPlaceholder": "Z.B. Alex",
24849
+ "process.delegateWhoPlaceholder": "Z. B. Alex",
24519
24850
  "process.delegateFollowUpLabel": "Weiterverfolgungs-Termin (Optional)",
24520
24851
  "process.delegateSendRequest": "Anfrage senden ...",
24521
24852
  "process.delegateMoveToWaiting": "Nach Warten verschieben",
@@ -24526,7 +24857,7 @@ var init_de = __esm(() => {
24526
24857
  "process.ifNotActionable": "Wenn es nicht ausführbar ist:",
24527
24858
  "process.waitingFor": "Auf wen oder was warten Sie?",
24528
24859
  "process.waitingForDesc": "Fügen Sie eine Notiz hinzu, um sich zu erinnern, auf was Sie warten",
24529
- "process.waitingPlaceholder": "Z.B. Auf Jonathan's Revision des Dokumentes warten ...",
24860
+ "process.waitingPlaceholder": "Z. B. Auf Jonathan's Revision des Dokumentes warten ...",
24530
24861
  "process.next": "Nächste",
24531
24862
  "process.noContext": "Kein Kontext",
24532
24863
  "process.project": "Zu einem Projekt hinzufügen?",
@@ -24576,7 +24907,7 @@ var init_de = __esm(() => {
24576
24907
  "settings.calendarSystemJalali": "Jalali (Sonnen-Hidschra)",
24577
24908
  "settings.keybindingsDesc": "Tastaturbelegung-Stil für den Desktop auswählen.",
24578
24909
  "settings.closeBehaviorDesc": "Wählen Sie, was beim Schließen des Fensters passieren soll.",
24579
- "settings.closeBehaviorAsk": "Jedesmal fragen",
24910
+ "settings.closeBehaviorAsk": "Jedes Mal fragen",
24580
24911
  "settings.closeBehaviorTray": "Im Taskbereich weiterlaufen lassen",
24581
24912
  "settings.closeBehaviorQuit": "Die App schließen",
24582
24913
  "settings.closeBehaviorPromptTitle": "Mindwtr schließen?",
@@ -24715,14 +25046,14 @@ var init_de = __esm(() => {
24715
25046
  "settings.aiApiKeyPlaceholder": "API-Schlüssel einfügen",
24716
25047
  "settings.speechTitle": "Sprache zu Text",
24717
25048
  "settings.speechDesc": "Sprachaufnahmen transkribieren und sie in Aufgabenfelder einteilen.",
24718
- "settings.speechEnable": "Sprache zu Text aktiviern",
25049
+ "settings.speechEnable": "Sprache zu Text aktivieren",
24719
25050
  "settings.speechProvider": "Spracherkennungs-Provider",
24720
25051
  "settings.speechProviderOffline": "Auf dem Gerät (Whisper)",
24721
25052
  "settings.speechModel": "Sprachmodell",
24722
25053
  "settings.speechBaseUrl": "Transkriptionsserver-URL",
24723
25054
  "settings.speechBaseUrlHint": "Für offizielles OpenAI leer lassen. Für einen selbst gehosteten OpenAI-kompatiblen Transkriptionsserver festlegen. API-Schlüssel optional.",
24724
25055
  "settings.speechOfflineModel": "Offline Modell",
24725
- "settings.speechOfflineModelDesc": "Einmal herunterlanden, um dann völlig offline zu transkribieren.",
25056
+ "settings.speechOfflineModelDesc": "Einmal herunterladen, um dann völlig offline zu transkribieren.",
24726
25057
  "settings.speechOfflineReady": "Modell heruntergeladen",
24727
25058
  "settings.speechOfflineNotDownloaded": "Modell nicht heruntergeladen",
24728
25059
  "settings.speechOfflineDownload": "Herunterladen",
@@ -24732,13 +25063,13 @@ var init_de = __esm(() => {
24732
25063
  "settings.speechOfflineDeleteError": "Offline-Modell löschen ist fehlgeschlagen",
24733
25064
  "settings.speechOfflineDeleteErrorBody": "Bitte nochmals versuchen.",
24734
25065
  "settings.speechLanguage": "Audio-Sprache",
24735
- "settings.speechLanguageHint": "Einen Sprachenamen oder Code angeben, oder frei lassen zur automatischen Bestimmung.",
25066
+ "settings.speechLanguageHint": "Einen Sprachnamen oder Code angeben, oder frei lassen zur automatischen Bestimmung.",
24736
25067
  "settings.speechLanguageAuto": "Automatisch (Sprache bestimmen)",
24737
25068
  "settings.speechMode": "Verarbeitungs-Modus",
24738
- "settings.speechModeHint": "Smart parse extrahiert automatisch Felder und Daten; Nur Transkripieren transkripiert einfach.",
24739
- "settings.speechModeTranscript": "Nur Transkripieren",
25069
+ "settings.speechModeHint": "'Smart parse' extrahiert automatisch Felder und Daten; 'Nur Transkribieren' transkribiert lediglich.",
25070
+ "settings.speechModeTranscript": "Nur Transkribieren",
24740
25071
  "settings.speechFieldStrategy": "Felder bestimmen",
24741
- "settings.speechFieldStrategyHint": "Auswählen, wo die Transkripierung standardmäßig gespeichert werden soll.",
25072
+ "settings.speechFieldStrategyHint": "Auswählen, wo die Transkription standardmäßig gespeichert werden soll.",
24742
25073
  "settings.speechFieldTitle": "Titel",
24743
25074
  "settings.speechFieldDescription": "Beschreibung",
24744
25075
  "settings.aiReasoning": "Begründungsaufwand",
@@ -24899,8 +25230,8 @@ var init_de = __esm(() => {
24899
25230
  "sort.label": "Sortieren",
24900
25231
  "sort.default": "Standard",
24901
25232
  "sort.due": "Fälligkeitsdatum",
24902
- "sort.start": "Beginn-Datum",
24903
- "sort.review": "Revisions-Datum",
25233
+ "sort.start": "Beginndatum",
25234
+ "sort.review": "Revisionsdatum",
24904
25235
  "sort.title": "Titel",
24905
25236
  "sort.timeEstimate": "Zeitabschätzung",
24906
25237
  "sort.created": "Älteste",
@@ -24923,7 +25254,7 @@ var init_de = __esm(() => {
24923
25254
  "agenda.focusHint": "Tippen Sie auf den Stern irgendeiner untenstehenden Aufgabe, um sie zum heutigen Fokus hinzuzufügen (max. 3).",
24924
25255
  "agenda.addToFocus": "Zum heutigen Fokus hinzufügen",
24925
25256
  "agenda.removeFromFocus": "Aus dem Fokus entfernen",
24926
- "agenda.maxFocusItems": "Max. 3 Fokus-Elemente",
25257
+ "agenda.maxFocusItems": "Max. {{count}} Fokus-Element(e)",
24927
25258
  "agenda.inProgress": "In Arbeit",
24928
25259
  "agenda.overdue": "Überfällig",
24929
25260
  "agenda.dueToday": "Heute fällig",
@@ -24953,7 +25284,7 @@ var init_de = __esm(() => {
24953
25284
  "waiting.moveToNext": "Nach Nächste verschieben",
24954
25285
  "waiting.markDone": "Als Erledigt markieren",
24955
25286
  "waiting.empty": "Es gibt nichts, worauf Sie warten müssten",
24956
- "waiting.emptyHint": "Speicheren Sie Aufgaben hier, wenn der nächste Schritt bei jemand anderem liegt – Sie werden sie sehen, bis sie erledigt sind",
25287
+ "waiting.emptyHint": "Speichern Sie Aufgaben hier, wenn der nächste Schritt bei jemand anderem liegt – Sie werden sie sehen, bis sie erledigt sind",
24957
25288
  "someday.title": "Irgendwann/Vielleicht",
24958
25289
  "someday.subtitle": "Ideen und Ziele, die Sie in Zukunft vielleicht verfolgen möchten",
24959
25290
  "someday.ideas": "Ideen",
@@ -24961,7 +25292,7 @@ var init_de = __esm(() => {
24961
25292
  "someday.moveToNext": "Zu Nächste verschieben",
24962
25293
  "someday.archive": "Archiv",
24963
25294
  "someday.empty": "Keine Irgendwann/Vielleicht-Elemente",
24964
- "someday.emptyHint": "Sammlen Sie hier Ideen und Ziele, die Sie vielleicht später verfolgen möchten – aus dem Blickfeld, aber nicht aus dem Kopf verlieren",
25295
+ "someday.emptyHint": "Sammeln Sie hier Ideen und Ziele, die Sie vielleicht später verfolgen möchten – aus dem Blickfeld, aber nicht aus dem Kopf verlieren",
24965
25296
  "search.title": "Suchen",
24966
25297
  "search.placeholder": "Aufgaben and Projekte suchen ...",
24967
25298
  "search.scopeHint": "Aufgaben, Projekte, Personen",
@@ -24974,10 +25305,10 @@ var init_de = __esm(() => {
24974
25305
  "search.showingFirst": "Zeigt {shown} von {total} Ergebnissen",
24975
25306
  "search.saveSearch": "Diese Suchabfrage speichern",
24976
25307
  "search.saveSearchPrompt": "Diese Suchabfrage benennen",
24977
- "search.savedSearches": "Sucheabfrage gespeichert",
25308
+ "search.savedSearches": "Suchabfrage gespeichert",
24978
25309
  "search.noSavedSearches": "Noch keine gespeicherte Suchabfragen.",
24979
25310
  "search.deleteConfirm": "Diese gespeicherte Suchabfrage löschen?",
24980
- "search.helpOperators": "Sie können Operatoren benutzen wie z.B. status:, context:, tag:, project:, due:<=7d.",
25311
+ "search.helpOperators": "Sie können Operatoren benutzen wie z. B. status:, context:, tag:, project:, due:<=7d.",
24981
25312
  "search.hiddenCompletedMatches": "{{count}} weitere in Erledigt und Archiviert",
24982
25313
  "search.completedDate": "Abgeschlossen {{date}}",
24983
25314
  "search.dueDate": "Fällig {{date}}",
@@ -25046,7 +25377,7 @@ var init_de = __esm(() => {
25046
25377
  "settings.sync": "Synchronisieren",
25047
25378
  "settings.syncDescription": "Richten Sie einen sekundären Ordner ein, mit dem Ihre Daten synchronisiert werden sollen (z. B. Dropbox, Syncthing). Für eine nahezu in Echtzeit erfolgende Synchronisierung auf mehreren Geräten wird WebDAV gegenüber Ordnersynchronisierungstools empfohlen.",
25048
25379
  "settings.attachmentsCleanup": "Bereinigung von Anhängen",
25049
- "settings.attachmentsCleanupDesc": "Entfernen Sie gelöschte oder verwaiste Anhangsdateien von Ihrem Gerät und synchronisieren Sie den Speicher.",
25380
+ "settings.attachmentsCleanupDesc": "Entfernen Sie gelöschte oder verwaiste Anhänge von Ihrem Gerät und synchronisieren Sie den Speicher.",
25050
25381
  "settings.attachmentsCleanupLastRun": "Letzte Bereinigung",
25051
25382
  "settings.attachmentsCleanupNever": "Nie",
25052
25383
  "settings.attachmentsCleanupRun": "Führen Sie die Bereinigung durch",
@@ -25075,7 +25406,7 @@ var init_de = __esm(() => {
25075
25406
  "settings.cloudProviderDropbox": "Dropbox",
25076
25407
  "settings.dropboxAppKey": "Dropbox-Konto",
25077
25408
  "settings.dropboxAppKeyHint": "Der Dropbox-App-Schlüssel wird zum Zeitpunkt der Erstellung/Veröffentlichung eingefügt.",
25078
- "settings.dropboxRedirectUri": "Umleitungs-URI",
25409
+ "settings.dropboxRedirectUri": "Redirect-URI",
25079
25410
  "settings.dropboxConnected": "Verbunden",
25080
25411
  "settings.dropboxNotConnected": "Nicht verbunden",
25081
25412
  "settings.dropboxConnect": "Dropbox verbinden",
@@ -25128,7 +25459,7 @@ var init_de = __esm(() => {
25128
25459
  "settings.downloadStarting": "Download wird geöffnet ...",
25129
25460
  "settings.downloadStarted": "Der Download wurde in Ihrem Browser gestartet.",
25130
25461
  "settings.downloadFailed": "Der Download-Link konnte nicht geöffnet werden.",
25131
- "settings.downloadChecksumMismatch": "Die Prüfsummenüberprüfung ist fehlgeschlagen. Bitte laden Sie die Datei erneut von der Release-Seite herunter.",
25462
+ "settings.downloadChecksumMismatch": "Die Überprüfung der Prüfsumme ist fehlgeschlagen. Bitte laden Sie die Datei erneut von der Release-Seite herunter.",
25132
25463
  "settings.downloadRecommended": "Empfohlenes Paket",
25133
25464
  "settings.downloadAURHint": "Arch erkannt: Aktualisierung über AUR",
25134
25465
  "settings.changelog": "Änderungsprotokoll",
@@ -27153,6 +27484,8 @@ var init_ja = __esm(() => {
27153
27484
  "attachments.fileTooLarge": "ファイルが大きすぎてアップロードできません。",
27154
27485
  "attachments.fileNotReadable": "このファイルを読み取れなかったため添付できませんでした。別のフォルダに移してからもう一度お試しください。",
27155
27486
  "attachments.linkToFile": "ファイルへのリンク…",
27487
+ "attachments.linkedFileElsewhere": "このリンクは別のデバイス上のファイルを指しています: {{path}}。そのデバイスで開くか、リンクではなくファイルを添付してください。",
27488
+ "attachments.openLinkFailed": "このリンクを開けませんでした。",
27156
27489
  "attachments.invalidFileType": "対応していないファイル形式です。",
27157
27490
  "attachments.invalidLink": "有効な URL を入力してください。",
27158
27491
  "attachments.photoUnavailableTitle": "写真の選択が利用できません",
@@ -27948,6 +28281,8 @@ var init_ja = __esm(() => {
27948
28281
  "settings.syncEncryptionUnlock": "パスフレーズを入力",
27949
28282
  "settings.syncEncryptionDecline": "後で",
27950
28283
  "settings.syncEncryptionPausedDesc": "パスフレーズを入力するまで、この端末の自動同期は停止したままになります。",
28284
+ "settings.syncEncryptionLockedRecheckHint": "この同期先に暗号化されたファイルがもうない場合は、「今すぐ同期」をタップしてください。この端末が同期先を再確認して続行します。",
28285
+ "settings.syncEncryptionNoEncryptedRemote": "この同期先には暗号化されたファイルがなくなったため、この端末の暗号化はオフになりました。この場所を暗号化するには、もう一度オンにしてください。",
27951
28286
  "settings.syncEncryptionRemoteEncrypted": "この同期先は暗号化されています。同期を続けるには、同期パスフレーズを入力してください。",
27952
28287
  "settings.syncEncryptionRemotePlaintext": "同期を停止しました: この同期先はもう暗号化されていません。このデバイスで同期の暗号化をオフにするか、同期先で暗号化をもう一度オンにしてください。",
27953
28288
  "settings.syncEncryptionRemotePlaintextDesc": "別のデバイスがこの同期先の暗号化をオフにしました。この端末側では何も変更も解除もされていません。平文のまま同期を続けるにはこのデバイスで同期の暗号化をオフにし、そうでなければ同期先で暗号化をもう一度オンにしてください。",
@@ -28897,6 +29232,12 @@ App Store に更新があります。アプリのページを開きますか?`
28897
29232
  "settings.syncMobile.accountStatus": "アカウントの状態",
28898
29233
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Dropbox の OAuth 設定に、このリダイレクト URI をそのまま登録してください。",
28899
29234
  "settings.syncMobile.backgroundSync": "バックグラウンド同期",
29235
+ "settings.syncMobile.backgroundSyncInterval": "バックグラウンド同期の間隔",
29236
+ "settings.syncMobile.backgroundSyncIntervalDescription": "アプリを閉じている間、システムはこの間隔より頻繁には実行しません。「オフ」にすると、アプリを開いているとき、アプリを離れたとき、編集直後のみ同期します。",
29237
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "15分ごと",
29238
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "6時間ごと",
29239
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "1時間ごと",
29240
+ "settings.syncMobile.backgroundSyncIntervalOff": "オフ",
28900
29241
  "settings.syncMobile.clearPendingAttachmentDeletes": "保留中の添付ファイル削除を取り消しますか?",
28901
29242
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "この端末では CloudKit が制限されています。スクリーンタイム、MDM、iCloud の制限を確認してから、もう一度お試しください。",
28902
29243
  "settings.syncMobile.connectedToDropbox": "Dropbox に接続しました。",
@@ -28984,6 +29325,9 @@ App Store に更新があります。アプリのページを開きますか?`
28984
29325
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV のエンドポイントに接続できました。",
28985
29326
  "settings.persistentCaptureLabel": "通知バーからクイックキャプチャ",
28986
29327
  "settings.persistentCaptureDesc": "常駐する通知を表示して、ロック画面を含めどこからでも書き留められるようにします。",
29328
+ "settings.exactAlarmsLabel": "リマインダーが遅れる可能性があります",
29329
+ "settings.exactAlarmsDesc": "Android が Mindwtr に正確なアラームを許可していないため、リマインダーが最大 1 分遅れることがあります。",
29330
+ "settings.exactAlarmsAllow": "許可",
28987
29331
  "settings.appSearchLabel": "システム検索に表示する",
28988
29332
  "settings.appSearchDesc": "Android のシステム検索から、進行中のタスク・プロジェクト・エリアをタイトルで探せるようにします。データが端末の外に出ることはありません。",
28989
29333
  "captureNotification.title": "クイックキャプチャ",
@@ -29318,6 +29662,8 @@ App Store に更新があります。アプリのページを開きますか?`
29318
29662
  "settings.gettingStartedContentContinueDesc": "ここでの作業が終わったあとでも、ガイド付きの「はじめかた」プロジェクトとサンプルのインボックス項目を追加できます。",
29319
29663
  "settings.syncSetupGuideTitle": "データと同期の設定ガイド",
29320
29664
  "settings.syncSetupGuideDesc": "Dropbox・iCloud・WebDAV・フォルダ同期・復旧についての手引き。",
29665
+ "settings.syncEncryptionGuideTitle": "同期の暗号化ガイド",
29666
+ "settings.syncEncryptionGuideDesc": "何を保護するか、対応するサーバー、パスフレーズの仕組み。",
29321
29667
  "settings.importSetupGuideTitle": "読み込みの設定ガイド",
29322
29668
  "settings.importSetupGuideDesc": "Todoist・TickTick・DGT GTD・OmniFocus・Mindwtr CSV・Apple リマインダー・バックアップからの読み込み方法。",
29323
29669
  "settings.backupDiagnostics.newerVersion": "このバックアップは、より新しいバージョンの Mindwtr({{version}})で作成されています。",
@@ -29340,7 +29686,7 @@ App Store に更新があります。アプリのページを開きますか?`
29340
29686
  "settings.importDiagnostics.unmappedDate": "{{count}}件の日付は対応づけられなかったため、取り込みませんでした。",
29341
29687
  "settings.importDiagnostics.unmappedStatus": "{{count}}件のステータスは対応づけられなかったため、安全な既定値を使いました。",
29342
29688
  "settings.importDiagnostics.unsupportedRecurrence": "対応していない繰り返しルール{{count}}件は、メモとして残しました。",
29343
- "settings.syncRemoteBusy": "別の互換性のある Mindwtr デバイスがこの同期先を更新しています。完了するまで待ってから、もう一度同期してください。",
29689
+ "settings.syncRemoteBusy": "別の Mindwtr デバイスがこの同期先を一時的に確保しています。同期は自動的に再試行されます。",
29344
29690
  "settings.syncRemoteCleanupDeferred": "同期処理は完了しました。Mindwtr は一時的な同期ロックを削除できませんでしたが、ロックは自動的に期限切れになります。再試行は不要です。",
29345
29691
  "settings.syncAttachmentWriteDeferred": "一部の添付ファイルの変更を完了できませんでした。不足しているローカルファイルを復元するか、該当する添付ファイルを削除してから、もう一度同期してください。",
29346
29692
  "settings.syncFileAttachmentTooLarge": "Mindwtr はローカルの添付ファイルを保持しました。File Sync で同期できる添付ファイルは 100 MB 未満です。小さいファイルに置き換えるか添付ファイルを削除してから、もう一度同期してください。",
@@ -37098,6 +37444,8 @@ var init_ko = __esm(() => {
37098
37444
  "attachments.fileTooLarge": "파일이 너무 커서 업로드할 수 없습니다.",
37099
37445
  "attachments.fileNotReadable": "이 파일을 읽을 수 없어 첨부되지 않았습니다. 파일을 다른 폴더로 옮긴 뒤 다시 시도하세요.",
37100
37446
  "attachments.linkToFile": "파일에 연결…",
37447
+ "attachments.linkedFileElsewhere": "이 링크는 다른 기기에 있는 파일을 가리킵니다: {{path}}. 해당 기기에서 열거나, 링크 대신 파일 자체를 첨부하세요.",
37448
+ "attachments.openLinkFailed": "이 링크를 열 수 없습니다.",
37101
37449
  "attachments.invalidFileType": "지원되지 않는 파일 형식입니다.",
37102
37450
  "attachments.invalidLink": "유효한 URL을 입력하세요.",
37103
37451
  "attachments.photoUnavailableTitle": "사진 선택 도구를 사용할 수 없습니다.",
@@ -38721,6 +39069,9 @@ App Store에 업데이트가 있습니다. 지금 앱 목록을 여시겠습니
38721
39069
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 엔드포인트에 접근할 수 있습니다.",
38722
39070
  "settings.persistentCaptureLabel": "알림 표시줄에서 빠른 수집",
38723
39071
  "settings.persistentCaptureDesc": "잠금 화면을 포함해 어디서나 수집할 수 있는 고정 알림을 유지합니다.",
39072
+ "settings.exactAlarmsLabel": "알림이 늦게 도착할 수 있습니다",
39073
+ "settings.exactAlarmsDesc": "Android가 Mindwtr에 정확한 알람을 허용하지 않아 알림이 최대 1분까지 늦게 울릴 수 있습니다.",
39074
+ "settings.exactAlarmsAllow": "허용",
38724
39075
  "settings.appSearchLabel": "시스템 검색에 노출",
38725
39076
  "settings.appSearchDesc": "Android 시스템 검색이 제목으로 활성 작업, 프로젝트, 영역을 찾을 수 있게 합니다. 어떤 데이터도 이 기기를 벗어나지 않습니다.",
38726
39077
  "captureNotification.title": "빠른 수집",
@@ -43188,6 +43539,8 @@ var init_fa = __esm(() => {
43188
43539
  "attachments.fileTooLarge": "حجم فایل برای بارگذاری زیاد است.",
43189
43540
  "attachments.fileNotReadable": "این فایل خوانده نشد، بنابراین پیوست نگردید. آن را به پوشه دیگری منتقل کرده و دوباره امتحان کنید.",
43190
43541
  "attachments.linkToFile": "پیوند به فایل…",
43542
+ "attachments.linkedFileElsewhere": "این پیوند به فایلی روی دستگاه دیگری اشاره می‌کند: {{path}}. آن را همان‌جا باز کنید یا به‌جای پیوند، خود فایل را پیوست کنید.",
43543
+ "attachments.openLinkFailed": "باز کردن این پیوند ممکن نبود.",
43191
43544
  "attachments.invalidFileType": "نوع فایل پشتیبانی نمی‌شود.",
43192
43545
  "attachments.invalidLink": "لطفاً یک آدرس معتبر وارد کنید.",
43193
43546
  "attachments.photoUnavailableTitle": "انتخاب‌گر عکس در دسترس نیست",
@@ -43982,6 +44335,8 @@ var init_fa = __esm(() => {
43982
44335
  "settings.syncEncryptionUnlock": "وارد کردن عبارت عبور",
43983
44336
  "settings.syncEncryptionDecline": "فعلاً نه",
43984
44337
  "settings.syncEncryptionPausedDesc": "تا زمانی که عبارت عبور را وارد نکنید، همگام‌سازی خودکار روی این دستگاه متوقف می‌ماند.",
44338
+ "settings.syncEncryptionLockedRecheckHint": "اگر این مکان همگام‌سازی دیگر فایل رمزگذاری‌شده ندارد، «همگام‌سازی اکنون» را بزنید؛ این دستگاه دوباره آن را بررسی می‌کند و ادامه می‌دهد.",
44339
+ "settings.syncEncryptionNoEncryptedRemote": "دیگر هیچ فایل رمزگذاری‌شده‌ای در این مکان همگام‌سازی وجود ندارد، بنابراین رمزگذاری روی این دستگاه اکنون خاموش است. برای رمزگذاری این مکان دوباره آن را روشن کنید.",
43985
44340
  "settings.syncEncryptionRemoteEncrypted": "این محل همگام‌سازی رمزگذاری شده است. برای ادامه همگام‌سازی، عبارت عبور آن را وارد کنید.",
43986
44341
  "settings.syncEncryptionRemotePlaintext": "همگام‌سازی متوقف شد: این محل همگام‌سازی دیگر رمزگذاری‌شده نیست. رمزگذاری همگام‌سازی را در این دستگاه خاموش کنید یا دوباره در محل همگام‌سازی روشن کنید.",
43987
44342
  "settings.syncEncryptionRemotePlaintextDesc": "دستگاه دیگری رمزگذاری را در این محل همگام‌سازی خاموش کرده است. هیچ چیزی در اینجا تغییر نکرده و از رمزگذاری خارج نشده است. برای ادامهٔ همگام‌سازی به شکل ساده، رمزگذاری همگام‌سازی را در این دستگاه خاموش کنید، یا رمزگذاری را دوباره در محل همگام‌سازی روشن کنید.",
@@ -44931,6 +45286,12 @@ var init_fa = __esm(() => {
44931
45286
  "settings.syncMobile.accountStatus": "وضعیت حساب",
44932
45287
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "دقیقاً همین آدرس بازگشت را در تنظیمات OAuth Dropbox اضافه کنید.",
44933
45288
  "settings.syncMobile.backgroundSync": "همگام‌سازی پس‌زمینه",
45289
+ "settings.syncMobile.backgroundSyncInterval": "بازه همگام‌سازی پس‌زمینه",
45290
+ "settings.syncMobile.backgroundSyncIntervalDescription": "سیستم این کار را حداکثر با این بازه، درحالی‌که برنامه بسته است، اجرا می‌کند. خاموش یعنی گوشی فقط وقتی برنامه باز است، وقتی آن را ترک می‌کنید و کمی پس از ویرایش‌ها همگام‌سازی می‌شود.",
45291
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "هر ۱۵ دقیقه",
45292
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "هر ۶ ساعت",
45293
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "هر ساعت",
45294
+ "settings.syncMobile.backgroundSyncIntervalOff": "خاموش",
44934
45295
  "settings.syncMobile.clearPendingAttachmentDeletes": "حذف‌های در انتظار پیوست پاک شوند؟",
44935
45296
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "CloudKit روی این دستگاه محدود شده است. Screen Time، MDM یا محدودیت‌های iCloud را بررسی کرده و دوباره امتحان کنید.",
44936
45297
  "settings.syncMobile.connectedToDropbox": "به Dropbox متصل شد.",
@@ -45018,6 +45379,9 @@ var init_fa = __esm(() => {
45018
45379
  "settings.syncMobile.webdavEndpointIsReachable": "نقطه پایانی WebDAV در دسترس است.",
45019
45380
  "settings.persistentCaptureLabel": "ثبت سریع در نوار اعلان",
45020
45381
  "settings.persistentCaptureDesc": "یک اعلان پایدار برای ثبت از هر جا، از جمله صفحه قفل، نگه دار.",
45382
+ "settings.exactAlarmsLabel": "یادآورها ممکن است دیر برسند",
45383
+ "settings.exactAlarmsDesc": "اندروید به Mindwtr اجازه‌ی تنظیم زنگ دقیق نمی‌دهد، بنابراین یادآورها ممکن است تا یک دقیقه دیرتر اجرا شوند.",
45384
+ "settings.exactAlarmsAllow": "اجازه بده",
45021
45385
  "settings.appSearchLabel": "نمایش در جستجوی سیستم",
45022
45386
  "settings.appSearchDesc": "به جستجوی سیستم اندروید اجازه بده کارها، پروژه‌ها و حوزه‌های فعال شما را بر اساس عنوان پیدا کند. هیچ داده‌ای از این دستگاه خارج نمی‌شود.",
45023
45387
  "captureNotification.title": "ثبت سریع",
@@ -45352,6 +45716,8 @@ var init_fa = __esm(() => {
45352
45716
  "settings.gettingStartedContentContinueDesc": "پس از پایان کار در اینجا نیز می‌توانید پروژه راهنمای «شروع کار» و موارد نمونه صندوق ورودی را اضافه کنید.",
45353
45717
  "settings.syncSetupGuideTitle": "راهنمای راه‌اندازی داده و همگام‌سازی",
45354
45718
  "settings.syncSetupGuideDesc": "نکات راه‌اندازی برای Dropbox، iCloud، WebDAV، همگام‌سازی فایل و بازیابی.",
45719
+ "settings.syncEncryptionGuideTitle": "راهنمای رمزگذاری همگام‌سازی",
45720
+ "settings.syncEncryptionGuideDesc": "چه چیزی را محافظت می‌کند، کدام سرورها از آن پشتیبانی می‌کنند و عبارت عبور چگونه کار می‌کند.",
45355
45721
  "settings.importSetupGuideTitle": "راهنمای راه‌اندازی واردکردن",
45356
45722
  "settings.importSetupGuideDesc": "روش‌های واردکردن پشتیبانی‌شده برای Todoist، TickTick، DGT GTD، OmniFocus، Mindwtr CSV، یادآورهای Apple و نسخه‌های پشتیبان.",
45357
45723
  "settings.backupDiagnostics.newerVersion": "این نسخه پشتیبان با نسخه جدیدتری از Mindwtr ({{version}}) ساخته شده است.",
@@ -45374,7 +45740,7 @@ var init_fa = __esm(() => {
45374
45740
  "settings.importDiagnostics.unmappedDate": "{{count}} مقدار تاریخ قابل تبدیل نبود و حذف شد.",
45375
45741
  "settings.importDiagnostics.unmappedStatus": "{{count}} مقدار وضعیت قابل تبدیل نبود و مقدار پیش‌فرض امن استفاده شد.",
45376
45742
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} قانون تکرار پشتیبانی‌نشده به‌صورت یادداشت نگه داشته شد.",
45377
- "settings.syncRemoteBusy": "یک دستگاه سازگار دیگرِ Mindwtr در حال به‌روزرسانی این مکان همگام‌سازی است. صبر کنید تا کارش تمام شود، سپس دوباره همگام‌سازی کنید.",
45743
+ "settings.syncRemoteBusy": "دستگاه Mindwtr دیگری این مکان همگام‌سازی را برای لحظه‌ای در اختیار دارد. همگام‌سازی به‌طور خودکار دوباره تلاش می‌کند.",
45378
45744
  "settings.syncRemoteCleanupDeferred": "عملیات همگام‌سازی کامل شد. Mindwtr نتوانست قفل موقت همگام‌سازی را حذف کند، اما این قفل خودکار منقضی می‌شود. نیازی به تلاش دوباره نیست.",
45379
45745
  "settings.syncAttachmentWriteDeferred": "برخی تغییرات پیوست‌ها کامل نشد. فایل‌های محلی گم‌شده را بازیابی کنید یا پیوست‌های مربوط را حذف کنید، سپس دوباره همگام‌سازی کنید.",
45380
45746
  "settings.syncFileAttachmentTooLarge": "Mindwtr پیوست محلی را نگه داشت. File Sync فقط می‌تواند پیوست‌های کوچک‌تر از ۱۰۰ مگابایت را همگام‌سازی کند. آن را با فایل کوچک‌تری جایگزین کنید یا پیوست را حذف کنید، سپس دوباره همگام‌سازی کنید.",
@@ -45681,6 +46047,8 @@ var init_sv = __esm(() => {
45681
46047
  "attachments.fileTooLarge": "Filen är för stor för att laddas upp.",
45682
46048
  "attachments.fileNotReadable": "Kunde inte läsa filen, så den bifogades inte. Flytta den till en annan mapp och försök igen.",
45683
46049
  "attachments.linkToFile": "Länk till fil…",
46050
+ "attachments.linkedFileElsewhere": "Den här länken pekar på en fil på en annan enhet: {{path}}. Öppna den där, eller bifoga filen i stället för att länka till den.",
46051
+ "attachments.openLinkFailed": "Kunde inte öppna den här länken.",
45684
46052
  "attachments.invalidFileType": "Filtypen stöds inte.",
45685
46053
  "attachments.invalidLink": "Ange en giltig URL.",
45686
46054
  "attachments.photoUnavailableTitle": "Bildväljaren är inte tillgänglig",
@@ -46475,6 +46843,8 @@ var init_sv = __esm(() => {
46475
46843
  "settings.syncEncryptionUnlock": "Ange lösenfras",
46476
46844
  "settings.syncEncryptionDecline": "Inte nu",
46477
46845
  "settings.syncEncryptionPausedDesc": "Automatisk synkronisering är pausad på den här enheten tills du anger lösenfrasen.",
46846
+ "settings.syncEncryptionLockedRecheckHint": "Om den här synkroniseringsplatsen inte längre innehåller krypterade filer trycker du på Synka nu. Enheten kontrollerar platsen på nytt och fortsätter.",
46847
+ "settings.syncEncryptionNoEncryptedRemote": "Det finns inga krypterade filer kvar på den här synkroniseringsplatsen, så krypteringen är nu av på den här enheten. Slå på den igen för att kryptera platsen.",
46478
46848
  "settings.syncEncryptionRemoteEncrypted": "Den här synkplatsen är krypterad. Ange synklösenfrasen för att fortsätta synka.",
46479
46849
  "settings.syncEncryptionRemotePlaintext": "Synkningen stoppades: den här synkplatsen är inte längre krypterad. Stäng av synkkryptering på den här enheten, eller slå på den igen på synkplatsen.",
46480
46850
  "settings.syncEncryptionRemotePlaintextDesc": "En annan enhet stängde av krypteringen på den här synkplatsen. Ingenting här har ändrats eller nedgraderats. Stäng av synkkryptering på den här enheten för att fortsätta synka i klartext, eller slå på krypteringen igen på synkplatsen.",
@@ -47424,6 +47794,12 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47424
47794
  "settings.syncMobile.accountStatus": "Kontostatus",
47425
47795
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Lägg till exakt den här omdirigerings-URI:n i Dropboxs OAuth-inställningar.",
47426
47796
  "settings.syncMobile.backgroundSync": "Bakgrundssynk",
47797
+ "settings.syncMobile.backgroundSyncInterval": "Intervall för bakgrundssynk",
47798
+ "settings.syncMobile.backgroundSyncIntervalDescription": "Systemet kör jobbet högst så här ofta medan appen är stängd. Av innebär att telefonen bara synkar när appen är öppen, när du lämnar den och strax efter ändringar.",
47799
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "Var 15:e minut",
47800
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "Var 6:e timme",
47801
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "Varje timme",
47802
+ "settings.syncMobile.backgroundSyncIntervalOff": "Av",
47427
47803
  "settings.syncMobile.clearPendingAttachmentDeletes": "Rensa väntande bilageborttagningar?",
47428
47804
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "CloudKit är begränsat på den här enheten. Kontrollera Skärmtid, MDM eller iCloud-begränsningar, och försök sedan igen.",
47429
47805
  "settings.syncMobile.connectedToDropbox": "Ansluten till Dropbox.",
@@ -47511,6 +47887,9 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47511
47887
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV-slutpunkten är nåbar.",
47512
47888
  "settings.persistentCaptureLabel": "Snabbinspelning i aviseringsfältet",
47513
47889
  "settings.persistentCaptureDesc": "Behåll en beständig avisering för att spela in varifrån som helst, inklusive låsskärmen.",
47890
+ "settings.exactAlarmsLabel": "Påminnelser kan komma sent",
47891
+ "settings.exactAlarmsDesc": "Android låter inte Mindwtr ställa in exakta alarm, så påminnelser kan komma upp till en minut för sent.",
47892
+ "settings.exactAlarmsAllow": "Tillåt",
47514
47893
  "settings.appSearchLabel": "Exponera för systemsökning",
47515
47894
  "settings.appSearchDesc": "Låt Androids systemsökning hitta dina aktiva uppgifter, projekt och områden via titel. Inget lämnar den här enheten.",
47516
47895
  "captureNotification.title": "Snabbinspelning",
@@ -47845,6 +48224,8 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47845
48224
  "settings.gettingStartedContentContinueDesc": "När du är klar här kan du fortfarande lägga till det guidade Kom igång-projektet och exempelposter i inkorgen.",
47846
48225
  "settings.syncSetupGuideTitle": "Guide för data- och synkroniseringsinställning",
47847
48226
  "settings.syncSetupGuideDesc": "Inställningsanteckningar för Dropbox, iCloud, WebDAV, filsynkronisering och återställning.",
48227
+ "settings.syncEncryptionGuideTitle": "Guide till synkroniseringskryptering",
48228
+ "settings.syncEncryptionGuideDesc": "Vad den skyddar, vilka servrar som stöder den och hur lösenfrasen fungerar.",
47848
48229
  "settings.importSetupGuideTitle": "Guide för importinställning",
47849
48230
  "settings.importSetupGuideDesc": "Importvägar som stöds för Todoist, TickTick, DGT GTD, OmniFocus, Mindwtr CSV, Apple Påminnelser och säkerhetskopior.",
47850
48231
  "settings.backupDiagnostics.newerVersion": "Den här säkerhetskopian skapades med en nyare version av Mindwtr ({{version}}).",
@@ -47867,7 +48248,7 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47867
48248
  "settings.importDiagnostics.unmappedDate": "{{count}} datumvärden kunde inte mappas och utelämnades.",
47868
48249
  "settings.importDiagnostics.unmappedStatus": "{{count}} statusvärden kunde inte mappas; ett säkert standardvärde användes.",
47869
48250
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} upprepningsregler som inte stöds sparades som anteckningar.",
47870
- "settings.syncRemoteBusy": "En annan kompatibel Mindwtr-enhet uppdaterar den här synkroniseringsplatsen. Vänta tills den är klar och synkronisera sedan igen.",
48251
+ "settings.syncRemoteBusy": "En annan Mindwtr-enhet håller den här synkroniseringsplatsen en kort stund. Synkroniseringen försöker igen av sig själv.",
47871
48252
  "settings.syncRemoteCleanupDeferred": "Synkroniseringen slutfördes. Mindwtr kunde inte ta bort det tillfälliga synkroniseringslåset, men det upphör automatiskt. Du behöver inte försöka igen.",
47872
48253
  "settings.syncAttachmentWriteDeferred": "Vissa ändringar av bilagor kunde inte slutföras. Återställ saknade lokala filer eller ta bort de berörda bilagorna och synkronisera sedan igen.",
47873
48254
  "settings.syncFileAttachmentTooLarge": "Mindwtr behöll den lokala bilagan. File Sync kan bara synkronisera bilagor som är mindre än 100 MB. Ersätt den med en mindre fil eller ta bort bilagan och synkronisera igen.",
@@ -48030,7 +48411,7 @@ var init_i18n_locales = __esm(() => {
48030
48411
  mode: "overrides",
48031
48412
  native: "한국어",
48032
48413
  nonLatin: true,
48033
- translatedKeyFloor: 2235
48414
+ translatedKeyFloor: 2240
48034
48415
  },
48035
48416
  it: {
48036
48417
  loadSync: () => (init_it(), __toCommonJS(exports_it)),
@@ -48297,7 +48678,9 @@ var init_settings_options = __esm(() => {
48297
48678
  });
48298
48679
 
48299
48680
  // ../../packages/core/src/async-utils.ts
48300
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), decodeUriSafe = (value) => {
48681
+ var sleepBypass = null, setSleepBypass = (predicate) => {
48682
+ sleepBypass = predicate;
48683
+ }, sleep = (ms) => sleepBypass?.() ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms)), decodeUriSafe = (value) => {
48301
48684
  try {
48302
48685
  return decodeURIComponent(value);
48303
48686
  } catch {
@@ -67433,11 +67816,14 @@ var DEFAULT_TIMEOUT_MS = 30000, SYNC_LOCAL_INSECURE_URL_OPTIONS, isAbortError =
67433
67816
  onProgress(offset, total);
67434
67817
  }
67435
67818
  });
67436
- }, NO_REDIRECT_METHODS, fetchWithTimeoutAndConsume = async (url, init, timeoutMs, fetcher, timeoutMessage, consume) => {
67819
+ }, NO_REDIRECT_METHODS, SUSPENDED_REQUEST_MESSAGE = "the request was interrupted while the app was suspended", SUSPENDED_REQUEST_FACTOR = 3, fetchWithTimeoutAndConsume = async (url, init, timeoutMs, fetcher, timeoutMessage, consume) => {
67437
67820
  const abortController = typeof AbortController === "function" ? new AbortController : null;
67438
67821
  let didTimeout = false;
67822
+ let firedAfterSuspension = false;
67823
+ const startedAt = Date.now();
67439
67824
  const timeoutId = abortController ? setTimeout(() => {
67440
67825
  didTimeout = true;
67826
+ firedAfterSuspension = Date.now() - startedAt > timeoutMs * SUSPENDED_REQUEST_FACTOR;
67441
67827
  abortController.abort(createAbortError(timeoutMessage));
67442
67828
  }, timeoutMs) : null;
67443
67829
  const signal = abortController?.signal ?? init.signal ?? undefined;
@@ -67472,7 +67858,7 @@ var DEFAULT_TIMEOUT_MS = 30000, SYNC_LOCAL_INSECURE_URL_OPTIONS, isAbortError =
67472
67858
  } catch (error2) {
67473
67859
  if (isAbortError(error2)) {
67474
67860
  if (didTimeout) {
67475
- throw new Error(timeoutMessage);
67861
+ throw new Error(firedAfterSuspension ? `${timeoutMessage}; ${SUSPENDED_REQUEST_MESSAGE}` : timeoutMessage);
67476
67862
  }
67477
67863
  if (externalSignal?.aborted) {
67478
67864
  throw getAbortSignalReason(externalSignal, "Request cancelled");
@@ -71986,6 +72372,8 @@ var init_en = __esm(() => {
71986
72372
  "attachments.fileTooLarge": "File is too large to upload.",
71987
72373
  "attachments.fileNotReadable": "Couldn't read this file, so it was not attached. Move it to a different folder and try again.",
71988
72374
  "attachments.linkToFile": "Link to file…",
72375
+ "attachments.linkedFileElsewhere": "This link points to a file on another device: {{path}}. Open it there, or attach the file instead of linking it.",
72376
+ "attachments.openLinkFailed": "Could not open this link.",
71989
72377
  "attachments.invalidFileType": "Unsupported file type.",
71990
72378
  "attachments.invalidLink": "Please enter a valid URL.",
71991
72379
  "attachments.photoUnavailableTitle": "Photo picker unavailable",
@@ -72781,6 +73169,8 @@ var init_en = __esm(() => {
72781
73169
  "settings.syncEncryptionUnlock": "Enter passphrase",
72782
73170
  "settings.syncEncryptionDecline": "Not now",
72783
73171
  "settings.syncEncryptionPausedDesc": "Automatic sync stays paused on this device until you enter the passphrase.",
73172
+ "settings.syncEncryptionLockedRecheckHint": "If this sync location no longer holds encrypted files, tap Sync now. This device re-checks the location and carries on.",
73173
+ "settings.syncEncryptionNoEncryptedRemote": "There are no encrypted files at this sync location any more, so encryption is now off on this device. Turn it on again to encrypt this location.",
72784
73174
  "settings.syncEncryptionRemoteEncrypted": "This sync location is encrypted. Enter its sync passphrase to continue syncing.",
72785
73175
  "settings.syncEncryptionRemotePlaintext": "Sync stopped: this sync location is no longer encrypted. Turn sync encryption off on this device, or turn it back on at the sync location.",
72786
73176
  "settings.syncEncryptionRemotePlaintextDesc": "Another device turned encryption off at this sync location. Nothing here has been changed or downgraded. Turn sync encryption off on this device to keep syncing in plain form, or turn encryption back on at the sync location.",
@@ -73730,6 +74120,12 @@ Update is available on the App Store. Open app listing now?`,
73730
74120
  "settings.syncMobile.accountStatus": "Account status",
73731
74121
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Add this exact redirect URI in Dropbox OAuth settings.",
73732
74122
  "settings.syncMobile.backgroundSync": "Background sync",
74123
+ "settings.syncMobile.backgroundSyncInterval": "Background sync interval",
74124
+ "settings.syncMobile.backgroundSyncIntervalDescription": "The system runs the job at most this often while the app is closed. Off means the phone syncs only while the app is open, when you leave it, and shortly after edits.",
74125
+ "settings.syncMobile.backgroundSyncIntervalEvery15Minutes": "Every 15 minutes",
74126
+ "settings.syncMobile.backgroundSyncIntervalEvery6Hours": "Every 6 hours",
74127
+ "settings.syncMobile.backgroundSyncIntervalEveryHour": "Every hour",
74128
+ "settings.syncMobile.backgroundSyncIntervalOff": "Off",
73733
74129
  "settings.syncMobile.clearPendingAttachmentDeletes": "Clear pending attachment deletes?",
73734
74130
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "CloudKit is restricted on this device. Check Screen Time, MDM, or iCloud restrictions, then try again.",
73735
74131
  "settings.syncMobile.connectedToDropbox": "Connected to Dropbox.",
@@ -73817,6 +74213,9 @@ Update is available on the App Store. Open app listing now?`,
73817
74213
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV endpoint is reachable.",
73818
74214
  "settings.persistentCaptureLabel": "Quick capture in notification bar",
73819
74215
  "settings.persistentCaptureDesc": "Keep a persistent notification to capture from anywhere, including the lock screen.",
74216
+ "settings.exactAlarmsLabel": "Reminders may arrive late",
74217
+ "settings.exactAlarmsDesc": "Android is not letting Mindwtr set exact alarms, so reminders can fire up to a minute late.",
74218
+ "settings.exactAlarmsAllow": "Allow",
73820
74219
  "settings.appSearchLabel": "Expose to system search",
73821
74220
  "settings.appSearchDesc": "Let Android system search find your active tasks, projects, and areas by title. Nothing leaves this device.",
73822
74221
  "captureNotification.title": "Quick capture",
@@ -74151,6 +74550,8 @@ Update is available on the App Store. Open app listing now?`,
74151
74550
  "settings.gettingStartedContentContinueDesc": "When you are done here, you can still add the guided Getting Started project and sample inbox items.",
74152
74551
  "settings.syncSetupGuideTitle": "Data & Sync setup guide",
74153
74552
  "settings.syncSetupGuideDesc": "Setup notes for Dropbox, iCloud, WebDAV, File Sync, and recovery.",
74553
+ "settings.syncEncryptionGuideTitle": "Sync encryption guide",
74554
+ "settings.syncEncryptionGuideDesc": "What it protects, which servers support it, and how the passphrase works.",
74154
74555
  "settings.importSetupGuideTitle": "Import setup guide",
74155
74556
  "settings.importSetupGuideDesc": "Supported Todoist, TickTick, DGT GTD, OmniFocus, Mindwtr CSV, Apple Reminders, and backup import paths.",
74156
74557
  "settings.backupDiagnostics.newerVersion": "This backup was created by a newer Mindwtr version ({{version}}).",
@@ -74173,7 +74574,7 @@ Update is available on the App Store. Open app listing now?`,
74173
74574
  "settings.importDiagnostics.unmappedDate": "{{count}} date value(s) could not be mapped and were omitted.",
74174
74575
  "settings.importDiagnostics.unmappedStatus": "{{count}} status value(s) could not be mapped and used a safe default.",
74175
74576
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} unsupported repeat rule(s) were kept as notes.",
74176
- "settings.syncRemoteBusy": "Another compatible Mindwtr device is updating this sync location. Wait for it to finish, then sync again.",
74577
+ "settings.syncRemoteBusy": "Another Mindwtr device is holding this sync location for a moment. Sync will retry on its own.",
74177
74578
  "settings.syncRemoteCleanupDeferred": "The sync operation completed. Mindwtr could not remove the temporary sync lock, but it expires automatically. No retry is needed.",
74178
74579
  "settings.syncAttachmentWriteDeferred": "Some attachment changes could not finish. Restore any missing local files or remove the affected attachments, then sync again.",
74179
74580
  "settings.syncFileAttachmentTooLarge": "Mindwtr kept the local attachment. File Sync can only sync attachments under 100 MB. Replace it with a smaller file or remove the attachment, then sync again.",
@@ -87719,6 +88120,7 @@ var init_sync_service_utils = __esm(() => {
87719
88120
  READONLY_ERROR_PATTERN = /isn't writable|not writable|read-only|read only|permission denied|EACCES/i;
87720
88121
  OFFLINE_ERROR_PATTERNS = [
87721
88122
  /offline state detected/i,
88123
+ /interrupted while the app was suspended/i,
87722
88124
  /network request failed/i,
87723
88125
  /internet connection appears to be offline/i,
87724
88126
  /airplane mode/i,
@@ -88093,6 +88495,56 @@ var MISSING_ATTACHMENT_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", MISSING_
88093
88495
  delete comparable[key];
88094
88496
  }
88095
88497
  return computeStableValueFingerprint(comparable);
88498
+ }, isDeepJsonEqual = (left, right) => {
88499
+ if (left === right)
88500
+ return true;
88501
+ if (left === null || right === null)
88502
+ return false;
88503
+ if (Array.isArray(left) || Array.isArray(right)) {
88504
+ if (!Array.isArray(left) || !Array.isArray(right))
88505
+ return false;
88506
+ if (left.length !== right.length)
88507
+ return false;
88508
+ for (let index = 0;index < left.length; index += 1) {
88509
+ if (!isDeepJsonEqual(left[index], right[index]))
88510
+ return false;
88511
+ }
88512
+ return true;
88513
+ }
88514
+ if (typeof left !== "object" || typeof right !== "object")
88515
+ return false;
88516
+ const leftRecord = left;
88517
+ const rightRecord = right;
88518
+ let definedInLeft = 0;
88519
+ for (const key of Object.keys(leftRecord)) {
88520
+ const leftValue = leftRecord[key];
88521
+ if (leftValue === undefined) {
88522
+ if (rightRecord[key] !== undefined)
88523
+ return false;
88524
+ continue;
88525
+ }
88526
+ if (!isDeepJsonEqual(leftValue, rightRecord[key]))
88527
+ return false;
88528
+ definedInLeft += 1;
88529
+ }
88530
+ let definedInRight = 0;
88531
+ for (const key of Object.keys(rightRecord)) {
88532
+ if (rightRecord[key] !== undefined)
88533
+ definedInRight += 1;
88534
+ }
88535
+ return definedInLeft === definedInRight;
88536
+ }, withoutSyncStatusBookkeeping = (settings) => {
88537
+ const comparable = { ...settings ?? {} };
88538
+ for (const key of SYNC_STATUS_BOOKKEEPING_SETTINGS_KEYS) {
88539
+ delete comparable[key];
88540
+ }
88541
+ return comparable;
88542
+ }, isLocalPersistEquivalent = (candidate, stored) => {
88543
+ if (candidate === stored)
88544
+ return true;
88545
+ if (!isDeepJsonEqual(withoutSyncStatusBookkeeping(candidate.settings), withoutSyncStatusBookkeeping(stored.settings)))
88546
+ return false;
88547
+ return isDeepJsonEqual({ ...candidate, settings: undefined }, { ...stored, settings: undefined });
88096
88548
  }, appendEntityRevisions = (parts, entities) => {
88097
88549
  parts.push(String(entities?.length ?? 0));
88098
88550
  for (const entity of entities ?? []) {
@@ -88544,6 +88996,17 @@ var hasOwnField2 = (value, field) => Object.prototype.hasOwnProperty.call(value,
88544
88996
  sequentialWithinSectionProjectIds,
88545
88997
  focusedProjectCount
88546
88998
  };
88999
+ }, isTaskCountedAsFocused = (task) => !task.deletedAt && task.isFocusedToday === true && task.status !== "done" && task.status !== "reference" && task.status !== "archived", focusedCountCache = null, selectFocusedCount = (tasks) => {
89000
+ if (focusedCountCache && focusedCountCache.tasks === tasks) {
89001
+ return focusedCountCache.count;
89002
+ }
89003
+ let count = 0;
89004
+ for (const task of tasks) {
89005
+ if (isTaskCountedAsFocused(task))
89006
+ count += 1;
89007
+ }
89008
+ focusedCountCache = { tasks, count };
89009
+ return count;
88547
89010
  }, computeTaskDerivedState = (tasks, tasksById) => {
88548
89011
  const resolvedTasksById = tasksById ?? new Map;
88549
89012
  const activeTasksByStatus = new Map;
@@ -88594,7 +89057,7 @@ var hasOwnField2 = (value, field) => Object.prototype.hasOwnProperty.call(value,
88594
89057
  if (dateCoherenceIssues.length > 0) {
88595
89058
  dateCoherenceIssuesByTaskId.set(task.id, dateCoherenceIssues);
88596
89059
  }
88597
- if (task.isFocusedToday && task.status !== "done" && task.status !== "reference" && task.status !== "archived") {
89060
+ if (isTaskCountedAsFocused(task)) {
88598
89061
  focusedCount += 1;
88599
89062
  focusedTasks.push(task);
88600
89063
  }
@@ -89806,6 +90269,7 @@ var normalizeAppData = (data) => ({
89806
90269
  isFocused: normalizeSyncedBoolean(project.isFocused),
89807
90270
  attachments: normalizeAttachmentsForSyncMerge(project.attachments),
89808
90271
  dueDate: normalizeOptionalString2(project.dueDate),
90272
+ startDate: normalizeOptionalString2(project.startDate),
89809
90273
  reviewAt: normalizeOptionalString2(project.reviewAt),
89810
90274
  areaId: normalizeOptionalString2(project.areaId),
89811
90275
  areaTitle: normalizeOptionalString2(project.areaTitle)
@@ -92012,6 +92476,23 @@ function createEmptyEntityStats(localTotal, incomingTotal) {
92012
92476
  conflictSamples: []
92013
92477
  };
92014
92478
  }
92479
+ function createLocalOnlyMergeStats(data) {
92480
+ const forCollection = (items) => {
92481
+ const stats = createEmptyEntityStats(items?.length ?? 0, 0);
92482
+ stats.mergedTotal = stats.localTotal;
92483
+ stats.localOnly = stats.localTotal;
92484
+ stats.resolvedUsingLocal = stats.localTotal;
92485
+ return stats;
92486
+ };
92487
+ return {
92488
+ tasks: forCollection(data.tasks),
92489
+ projects: forCollection(data.projects),
92490
+ sections: forCollection(data.sections),
92491
+ areas: forCollection(data.areas),
92492
+ people: forCollection(data.people),
92493
+ tombstoneRepairs: 0
92494
+ };
92495
+ }
92015
92496
  function mergeEntitiesWithStats(local, incoming, mergeConflict, normalizeForComparison, entityType = "entity", nowIso) {
92016
92497
  const localMap = new Map(local.map((item) => [item.id, item]));
92017
92498
  const incomingMap = new Map(incoming.map((item) => [item.id, item]));
@@ -92666,7 +93147,8 @@ async function performSyncCycleUnlocked(io) {
92666
93147
  const remoteData = purgeExpiredTombstones(remoteDocument.data, nowIso, io.tombstoneRetentionDays).data;
92667
93148
  io.onStep?.("merge");
92668
93149
  await yieldToUi();
92669
- const mergeResult = mergeAppDataWithStats(localData, remoteData, {
93150
+ const remoteIsEmpty = remoteData.tasks.length === 0 && remoteData.projects.length === 0 && remoteData.sections.length === 0 && remoteData.areas.length === 0 && (remoteData.people?.length ?? 0) === 0;
93151
+ const mergeResult = io.skipEmptyRemoteMerge?.() === true && remoteIsEmpty ? { data: localData, stats: createLocalOnlyMergeStats(localData) } : mergeAppDataWithStats(localData, remoteData, {
92670
93152
  nowIso,
92671
93153
  preferIncomingAttachmentCloudKeys: io.preferIncomingAttachmentCloudKeys
92672
93154
  });
@@ -92754,6 +93236,34 @@ async function performSyncCycleUnlocked(io) {
92754
93236
  throw new Error(`Sync validation failed: ${sample}`);
92755
93237
  }
92756
93238
  }
93239
+ if (!pendingRemoteWriteMeta && typeof io.isLocalPersistUnchanged === "function" && typeof io.persistSyncStatusOnly === "function" && io.isLocalPersistUnchanged(finalData)) {
93240
+ io.onStep?.("write-remote");
93241
+ await yieldToUi();
93242
+ try {
93243
+ await io.writeRemote(finalData);
93244
+ } catch (error2) {
93245
+ if (!isLocalSyncAbortError2(error2)) {
93246
+ io.onStep?.("write-local");
93247
+ await yieldToUi();
93248
+ await io.writeLocal(withPendingRemoteWriteRetry(finalData, nowIso, error2));
93249
+ }
93250
+ throw error2;
93251
+ }
93252
+ try {
93253
+ await io.persistSyncStatusOnly(finalData);
93254
+ } catch (error2) {
93255
+ logWarn("Failed to persist sync status after an unchanged local write", {
93256
+ error: error2 instanceof Error ? error2.message : String(error2)
93257
+ });
93258
+ }
93259
+ return {
93260
+ data: finalData,
93261
+ stats: mergeResult.stats,
93262
+ status: nextSyncStatus,
93263
+ clockSkewWarning: mergeResult.clockSkewWarning,
93264
+ localWriteSkipped: true
93265
+ };
93266
+ }
92757
93267
  const finalDataWithPendingRemoteWrite = withPendingRemoteWriteFlag(finalData, pendingRemoteWriteMeta?.pendingAt ?? nowIso, pendingRemoteWriteMeta?.attempts);
92758
93268
  io.onStep?.("write-local");
92759
93269
  await yieldToUi();
@@ -94587,6 +95097,8 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94587
95097
  order: 0,
94588
95098
  tagIds: [],
94589
95099
  supportNotes: resolveStarterString(lang, STARTER_PROJECT_NOTES_KEY),
95100
+ isSequential: false,
95101
+ isFocused: false,
94590
95102
  rev: 1,
94591
95103
  ...revisionMeta,
94592
95104
  createdAt: nowIso,
@@ -94610,7 +95122,9 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94610
95122
  projectId,
94611
95123
  order: index,
94612
95124
  orderNum: index,
94613
- isFocusedToday: template.isFocusedToday,
95125
+ isFocusedToday: template.isFocusedToday ?? false,
95126
+ suppressMindwtrReminders: false,
95127
+ pushCount: 0,
94614
95128
  rev: 1,
94615
95129
  ...revisionMeta,
94616
95130
  createdAt: nowIso,
@@ -94623,6 +95137,9 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94623
95137
  taskMode: "task",
94624
95138
  tags: [],
94625
95139
  contexts: [],
95140
+ isFocusedToday: false,
95141
+ suppressMindwtrReminders: false,
95142
+ pushCount: 0,
94626
95143
  rev: 1,
94627
95144
  ...revisionMeta,
94628
95145
  createdAt: nowIso,
@@ -95261,7 +95778,8 @@ var STORAGE_TIMEOUT_MS = 15000, SLOW_FETCH_LOG_THRESHOLD_MS = 1000, getFetchData
95261
95778
  },
95262
95779
  setHighlightTask: (id2) => {
95263
95780
  set5({ highlightTaskId: id2, highlightTaskAt: id2 ? Date.now() : null });
95264
- }
95781
+ },
95782
+ getFocusedCount: () => selectFocusedCount(get().tasks)
95265
95783
  });
95266
95784
  var init_store_settings = __esm(() => {
95267
95785
  init_logger();
@@ -95514,17 +96032,22 @@ var createAreaActions = ({
95514
96032
  const nextName = updates.name !== undefined ? updates.name.trim() : area.name;
95515
96033
  let projectsChanged = false;
95516
96034
  let newAllProjects = state._allProjects;
95517
- if ("color" in updates) {
95518
- const nextAreaColor = updates.color ?? DEFAULT_PROJECT_COLOR;
96035
+ const repaintColor = "color" in updates;
96036
+ const nextAreaColor = updates.color ?? DEFAULT_PROJECT_COLOR;
96037
+ const nextAreaTitle = nextName.trim() || undefined;
96038
+ if (repaintColor || nextAreaTitle !== area.name?.trim()) {
95519
96039
  newAllProjects = state._allProjects.map((project) => {
95520
96040
  if (project.areaId !== id2)
95521
96041
  return project;
95522
- if (project.color === nextAreaColor)
96042
+ const wantsColor = repaintColor && project.color !== nextAreaColor;
96043
+ const wantsTitle = project.areaTitle !== nextAreaTitle;
96044
+ if (!wantsColor && !wantsTitle)
95523
96045
  return project;
95524
96046
  projectsChanged = true;
95525
96047
  return {
95526
96048
  ...project,
95527
- color: nextAreaColor,
96049
+ ...wantsColor ? { color: nextAreaColor } : {},
96050
+ ...wantsTitle ? { areaTitle: nextAreaTitle } : {},
95528
96051
  updatedAt: now3,
95529
96052
  rev: nextRevision(project.rev),
95530
96053
  revBy: deviceState.deviceId
@@ -95601,7 +96124,7 @@ var createAreaActions = ({
95601
96124
  };
95602
96125
  });
95603
96126
  const newAllTasks = state._allTasks.map((task) => {
95604
- if (task.areaId !== id2 || task.deletedAt)
96127
+ if (task.areaId !== id2)
95605
96128
  return task;
95606
96129
  return {
95607
96130
  ...task,
@@ -95928,6 +96451,7 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95928
96451
  color,
95929
96452
  initialProps,
95930
96453
  existingProjects,
96454
+ existingAreas,
95931
96455
  settings,
95932
96456
  deviceId,
95933
96457
  now: now3,
@@ -95935,11 +96459,11 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95935
96459
  }) => {
95936
96460
  const trimmedTitle = typeof title === "string" ? title.trim() : "";
95937
96461
  const targetAreaId = initialProps?.areaId;
95938
- const maxOrder = existingProjects.filter((project) => (project.areaId ?? undefined) === (targetAreaId ?? undefined)).reduce((max3, project) => Math.max(max3, Number.isFinite(project.order) ? project.order : -1), -1);
96462
+ const maxOrder = existingProjects.filter((project2) => (project2.areaId ?? undefined) === (targetAreaId ?? undefined)).reduce((max3, project2) => Math.max(max3, Number.isFinite(project2.order) ? project2.order : -1), -1);
95939
96463
  const baseOrder = Number.isFinite(initialProps?.order) ? initialProps?.order : maxOrder + 1;
95940
96464
  const hasExplicitFlowMode = Boolean(initialProps && Object.prototype.hasOwnProperty.call(initialProps, "isSequential"));
95941
96465
  const useSequentialDefault = !hasExplicitFlowMode && settings.gtd?.defaultProjectFlowMode === "sequential";
95942
- return {
96466
+ const project = {
95943
96467
  id: id2 ?? generateUUID(),
95944
96468
  title: trimmedTitle,
95945
96469
  color: color ?? DEFAULT_PROJECT_COLOR,
@@ -95949,10 +96473,14 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95949
96473
  revBy: deviceId,
95950
96474
  createdAt: now3,
95951
96475
  updatedAt: now3,
96476
+ isSequential: false,
96477
+ isFocused: false,
95952
96478
  ...useSequentialDefault ? { isSequential: true } : {},
95953
96479
  ...initialProps,
95954
96480
  tagIds: initialProps?.tagIds ?? []
95955
96481
  };
96482
+ const areaTitle = project.areaId ? existingAreas.find((area) => area.id === project.areaId && !area.deletedAt)?.name?.trim() || undefined : undefined;
96483
+ return areaTitle === project.areaTitle ? project : { ...project, areaTitle };
95956
96484
  }, createProjectCoreActions = ({
95957
96485
  set: set5,
95958
96486
  get,
@@ -95981,6 +96509,7 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95981
96509
  color,
95982
96510
  initialProps,
95983
96511
  existingProjects: state._allProjects,
96512
+ existingAreas: state._allAreas,
95984
96513
  settings: state.settings,
95985
96514
  deviceId: deviceState.deviceId,
95986
96515
  now: now3
@@ -96662,7 +97191,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96662
97191
  const now3 = new Date().toISOString();
96663
97192
  const projectOrderReserver = createProjectOrderReserver(currentState._allTasks);
96664
97193
  const focusTaskLimit = normalizeFocusTaskLimit(currentState.settings.gtd?.focusTaskLimit);
96665
- let focusedCount = currentState.getDerivedState().focusedCount;
97194
+ let focusedCount = currentState.getFocusedCount();
96666
97195
  const nextAllTasks = [...currentState._allTasks];
96667
97196
  const newTasks = [];
96668
97197
  for (const item of normalizedItems) {
@@ -96708,6 +97237,8 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96708
97237
  updatedAt: now3,
96709
97238
  deletedAt: undefined,
96710
97239
  purgedAt: undefined,
97240
+ isFocusedToday: initialTaskProps.isFocusedToday ?? false,
97241
+ suppressMindwtrReminders: initialTaskProps.suppressMindwtrReminders ?? false,
96711
97242
  ...referenceClears,
96712
97243
  areaId: resolvedAreaId,
96713
97244
  projectId: resolvedProjectId,
@@ -96776,7 +97307,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96776
97307
  const isPromotingTaskFocus = preparedUpdates.updates.isFocusedToday === true && existingTask.isFocusedToday !== true;
96777
97308
  if (isPromotingTaskFocus) {
96778
97309
  const focusTaskLimit = normalizeFocusTaskLimit(currentState.settings.gtd?.focusTaskLimit);
96779
- const focusedCount = currentState.getDerivedState().focusedCount;
97310
+ const focusedCount = currentState.getFocusedCount();
96780
97311
  if (focusedCount >= focusTaskLimit) {
96781
97312
  const message3 = `Focus limit of ${focusTaskLimit} reached`;
96782
97313
  set5({ error: message3 });
@@ -97187,6 +97718,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
97187
97718
  tagIds: projectTagIds
97188
97719
  },
97189
97720
  existingProjects: state._allProjects,
97721
+ existingAreas: state._allAreas,
97190
97722
  settings: state.settings,
97191
97723
  deviceId: deviceState.deviceId,
97192
97724
  now: now3
@@ -98832,7 +99364,7 @@ var DEFAULT_ATTACHMENT_CLEANUP_INTERVAL_MS, CLOUD_PROVIDER_SELF_HOSTED = "selfho
98832
99364
  return currentChangeAt;
98833
99365
  onStale?.({ localSnapshotChangeAt, currentChangeAt });
98834
99366
  requestFollowUp();
98835
- throw new LocalSyncAbort;
99367
+ throw new LocalSyncAbort("local-data-changed");
98836
99368
  }, getInMemoryAppDataSnapshot = () => {
98837
99369
  const state = useTaskStore.getState();
98838
99370
  return cloneAppData({
@@ -98913,9 +99445,11 @@ var init_sync_client_helpers = __esm(() => {
98913
99445
  init_sync_runtime_utils();
98914
99446
  DEFAULT_ATTACHMENT_CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
98915
99447
  LocalSyncAbort = class LocalSyncAbort extends Error {
98916
- constructor() {
99448
+ reason;
99449
+ constructor(reason = "local-data-changed") {
98917
99450
  super("Local changes detected during sync");
98918
99451
  this.name = "LocalSyncAbort";
99452
+ this.reason = reason;
98919
99453
  }
98920
99454
  };
98921
99455
  });
@@ -100996,21 +101530,26 @@ function getSyncEncryptionStatusFromLocalState(localState) {
100996
101530
  incompleteTransition: persisted.incompleteTransition
100997
101531
  };
100998
101532
  }
100999
- function markRemoteEncryptionDiscovered(localState, discovered) {
101533
+ function markRemoteEncryptionDiscovered(localState, discovered, scope) {
101000
101534
  const current = localState.read();
101001
101535
  if (current && SYNC_ENCRYPTION_KEYED_STATES.includes(current.state) && current.discoveredSalt === bytesToHex(discovered.salt))
101002
101536
  return;
101003
101537
  localState.write({
101004
101538
  state: "remote-encrypted-no-key",
101005
101539
  discoveredSalt: bytesToHex(discovered.salt),
101006
- discoveredParams: discovered.params
101540
+ discoveredParams: discovered.params,
101541
+ ...scope ? { discoveredScope: scope } : {}
101007
101542
  });
101008
101543
  }
101009
- function markRemotePlaintextDiscovered(localState) {
101544
+ function markRemotePlaintextDiscovered(localState, scope) {
101010
101545
  const current = localState.read();
101011
101546
  if (!current || current.state !== "enabled")
101012
101547
  return;
101013
- localState.write({ ...current, state: "remote-plaintext" });
101548
+ localState.write({
101549
+ ...current,
101550
+ state: "remote-plaintext",
101551
+ ...scope ? { discoveredScope: scope } : {}
101552
+ });
101014
101553
  }
101015
101554
  function reaffirmRemoteEncryptionNoKey(localState) {
101016
101555
  const current = localState.read();
@@ -101419,6 +101958,10 @@ async function runProvideSyncEncryptionPassphraseOverRemote(passphrase, baseDocu
101419
101958
  const captured = await remote.read(encName);
101420
101959
  const bytes = captured.bytes;
101421
101960
  if (!bytes) {
101961
+ if (localState.read()?.state === "remote-encrypted-no-key") {
101962
+ await runDisableSyncEncryptionLocalOnly(keyCache, localState);
101963
+ return "no-encrypted-remote";
101964
+ }
101422
101965
  throw new Error(`sync encryption: no encrypted remote artifact found at ${encName}`);
101423
101966
  }
101424
101967
  requireExistingRemoteVersion(encName, captured);
@@ -101454,7 +101997,42 @@ async function runProvideSyncEncryptionPassphraseOverRemote(passphrase, baseDocu
101454
101997
  }
101455
101998
  throw new SyncEncryptionRemoteConflictError(`${encName} changed during passphrase validation`);
101456
101999
  }
101457
- var SYNC_ENCRYPTION_KEYED_STATES, SYNC_ENCRYPTION_TRANSITION_INCOMPLETE = "SYNC_ENCRYPTION_TRANSITION_INCOMPLETE", SyncEncryptionTransitionIncompleteError, requireMatchingIncompleteTransition = (localState, kind) => {
102000
+ var SYNC_ENCRYPTION_KEYED_STATES, scopeValue = (value) => {
102001
+ const trimmed = value?.trim();
102002
+ return trimmed ? trimmed : null;
102003
+ }, buildSyncLocationScope = (input) => {
102004
+ const backend = scopeValue(input.backend) ?? "off";
102005
+ if (backend === "file")
102006
+ return JSON.stringify(["file", scopeValue(input.syncPath)]);
102007
+ if (backend === "webdav") {
102008
+ const url = scopeValue(input.webdavUrl);
102009
+ return JSON.stringify([
102010
+ "webdav",
102011
+ url ? normalizeWebdavUrl(url) : null,
102012
+ scopeValue(input.webdavUsername)
102013
+ ]);
102014
+ }
102015
+ if (backend === "cloud") {
102016
+ const provider = scopeValue(input.cloudProvider) ?? "selfhosted";
102017
+ if (provider === "dropbox")
102018
+ return JSON.stringify(["cloud", "dropbox"]);
102019
+ const url = scopeValue(input.cloudUrl);
102020
+ return JSON.stringify(["cloud", provider, url ? normalizeCloudUrl(url) : null]);
102021
+ }
102022
+ return JSON.stringify([backend]);
102023
+ }, isSyncEncryptionStateBlocked = (state, activeScope) => {
102024
+ if (!state)
102025
+ return false;
102026
+ if (state.incompleteTransition)
102027
+ return true;
102028
+ if (state.state !== "remote-encrypted-no-key" && state.state !== "remote-plaintext")
102029
+ return false;
102030
+ if (!state.discoveredScope)
102031
+ return false;
102032
+ if (activeScope === null)
102033
+ return true;
102034
+ return state.discoveredScope === activeScope;
102035
+ }, SYNC_ENCRYPTION_TRANSITION_INCOMPLETE = "SYNC_ENCRYPTION_TRANSITION_INCOMPLETE", SyncEncryptionTransitionIncompleteError, requireMatchingIncompleteTransition = (localState, kind) => {
101458
102036
  const current = localState.read();
101459
102037
  if (current?.incompleteTransition && current.incompleteTransition !== kind) {
101460
102038
  throw new SyncEncryptionTransitionIncompleteError(current.incompleteTransition);
@@ -101530,6 +102108,7 @@ var SYNC_ENCRYPTION_KEYED_STATES, SYNC_ENCRYPTION_TRANSITION_INCOMPLETE = "SYNC_
101530
102108
  }, PLAINTEXT_PAD_BYTE = 32, __syncEncryptionTestUtils;
101531
102109
  var init_sync_encryption = __esm(() => {
101532
102110
  init_sync_crypto();
102111
+ init_sync_helpers();
101533
102112
  SYNC_ENCRYPTION_KEYED_STATES = ["enabled", "remote-plaintext"];
101534
102113
  SyncEncryptionTransitionIncompleteError = class SyncEncryptionTransitionIncompleteError extends Error {
101535
102114
  constructor(kind) {
@@ -102142,15 +102721,18 @@ var WEBDAV_REMOTE_WRITE_CONFLICT = "WEBDAV_REMOTE_WRITE_CONFLICT", WebDavRemoteW
102142
102721
  }
102143
102722
  }, __webdavTestUtils, getWebdavParentCollectionUrl = (url) => {
102144
102723
  try {
102145
- const parsed = new URL(url);
102146
- const trimmedPath = parsed.pathname.replace(/\/+$/, "");
102147
- const lastSlash = trimmedPath.lastIndexOf("/");
102148
- if (lastSlash <= 0)
102724
+ const suffixStart = url.search(/[?#]/);
102725
+ const withoutSuffix = (suffixStart === -1 ? url : url.slice(0, suffixStart)).replace(/\/+$/, "");
102726
+ const schemeEnd = withoutSuffix.indexOf("://");
102727
+ if (schemeEnd === -1)
102149
102728
  return null;
102150
- parsed.pathname = trimmedPath.slice(0, lastSlash);
102151
- parsed.search = "";
102152
- parsed.hash = "";
102153
- return parsed.toString().replace(/\/+$/, "");
102729
+ const pathStart = withoutSuffix.indexOf("/", schemeEnd + 3);
102730
+ if (pathStart === -1)
102731
+ return null;
102732
+ const lastSlash = withoutSuffix.lastIndexOf("/");
102733
+ if (lastSlash <= pathStart)
102734
+ return null;
102735
+ return withoutSuffix.slice(0, lastSlash);
102154
102736
  } catch {
102155
102737
  return null;
102156
102738
  }
@@ -102455,7 +103037,7 @@ async function acquireSyncRemoteMutationFence(port, options) {
102455
103037
  if (!Number.isFinite(ttlMs) || ttlMs < MIN_TTL_MS || ttlMs > MAX_TTL_MS) {
102456
103038
  throw new Error(`Remote sync mutation fence ttlMs must be between ${MIN_TTL_MS} and ${MAX_TTL_MS}`);
102457
103039
  }
102458
- const heartbeatMs = options.heartbeatMs ?? Math.max(1000, Math.floor(ttlMs / 3));
103040
+ const heartbeatMs = options.heartbeatMs ?? Math.max(1000, Math.min(DEFAULT_HEARTBEAT_MS, Math.floor(ttlMs / 3)));
102459
103041
  if (!Number.isFinite(heartbeatMs) || heartbeatMs < 0 || heartbeatMs >= ttlMs) {
102460
103042
  throw new Error("Remote sync mutation fence heartbeatMs must be zero or shorter than ttlMs");
102461
103043
  }
@@ -102468,16 +103050,27 @@ async function acquireSyncRemoteMutationFence(port, options) {
102468
103050
  throw new Error("Remote sync mutation fence leaseId is too short");
102469
103051
  let acquiredVersion = null;
102470
103052
  let acquiredRemainingMs = ttlMs;
103053
+ let reclaimedFrom = null;
102471
103054
  for (let attempt = 0;attempt < maxAttempts; attempt += 1) {
102472
103055
  const snapshot = await port.read();
102473
103056
  const serverNowMs = requireServerNow(snapshot);
103057
+ reclaimedFrom = null;
102474
103058
  if (snapshot.bytes) {
102475
103059
  if (!snapshot.version) {
102476
103060
  throw new SyncRemoteMutationFenceUnavailableError("Existing remote sync mutation fence has no safe version");
102477
103061
  }
102478
103062
  const current = parseRecord(snapshot.bytes);
102479
103063
  if (current.expiresAt > serverNowMs && !isImpossibleFutureExpiry(current.expiresAt, serverNowMs)) {
102480
- throw new SyncRemoteMutationFenceBusyError(current.expiresAt - serverNowMs);
103064
+ const holder = {
103065
+ ownerId: current.ownerId,
103066
+ leaseId: current.leaseId,
103067
+ purpose: current.purpose,
103068
+ remainingMs: current.expiresAt - serverNowMs
103069
+ };
103070
+ if (!isAbandonedRecord(current, serverNowMs)) {
103071
+ throw new SyncRemoteMutationFenceBusyError(current.expiresAt - serverNowMs, holder);
103072
+ }
103073
+ reclaimedFrom = holder;
102481
103074
  }
102482
103075
  } else if (snapshot.version !== null) {
102483
103076
  throw new SyncRemoteMutationFenceUnavailableError("Missing remote sync mutation fence unexpectedly has a version");
@@ -102487,7 +103080,9 @@ async function acquireSyncRemoteMutationFence(port, options) {
102487
103080
  leaseId,
102488
103081
  ownerId,
102489
103082
  purpose: options.purpose,
102490
- expiresAt: serverNowMs + ttlMs
103083
+ expiresAt: serverNowMs + ttlMs,
103084
+ heartbeatMs,
103085
+ renewedAt: serverNowMs
102491
103086
  };
102492
103087
  try {
102493
103088
  await port.write(encodeRecord(record3), snapshot.version);
@@ -102559,7 +103154,9 @@ async function acquireSyncRemoteMutationFence(port, options) {
102559
103154
  leaseId,
102560
103155
  ownerId,
102561
103156
  purpose: options.purpose,
102562
- expiresAt: serverNowMs + ttlMs
103157
+ expiresAt: serverNowMs + ttlMs,
103158
+ heartbeatMs,
103159
+ renewedAt: serverNowMs
102563
103160
  };
102564
103161
  try {
102565
103162
  await port.write(encodeRecord(replacement), snapshot.version);
@@ -102601,6 +103198,7 @@ async function acquireSyncRemoteMutationFence(port, options) {
102601
103198
  };
102602
103199
  scheduleHeartbeat();
102603
103200
  return {
103201
+ reclaimedFrom,
102604
103202
  assertHeld: (minRemainingMs = 0) => serialize(async () => {
102605
103203
  if (!Number.isFinite(minRemainingMs) || minRemainingMs < 0 || minRemainingMs >= ttlMs) {
102606
103204
  throw new Error("Remote sync mutation fence remaining-time requirement is invalid");
@@ -102642,12 +103240,19 @@ async function acquireSyncRemoteMutationFence(port, options) {
102642
103240
  })
102643
103241
  };
102644
103242
  }
102645
- var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE_MUTATION_REQUEST_HORIZON_MS = 35000, SyncRemoteMutationFenceBusyError, SyncRemoteMutationFenceLostError, SyncRemoteMutationFenceUnavailableError, isSyncRemoteMutationFenceError = (error2) => error2 instanceof SyncRemoteMutationFenceBusyError || error2 instanceof SyncRemoteMutationFenceLostError || error2 instanceof SyncRemoteMutationFenceUnavailableError, FENCE_SCHEMA = 1, MIN_TTL_MS = 1e4, MAX_TTL_MS, MAX_FUTURE_EXPIRY_TOLERANCE_MS = 60000, MAX_RECORD_BYTES = 4096, DEFAULT_ACQUIRE_ATTEMPTS = 4, encodeUtf8 = (text) => new TextEncoder().encode(text), decodeUtf8 = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes), requireServerNow = (snapshot) => {
103243
+ var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE_MUTATION_REQUEST_HORIZON_MS = 35000, SyncRemoteMutationFenceBusyError, SyncRemoteMutationFenceLostError, SyncRemoteMutationFenceUnavailableError, isSyncRemoteMutationFenceError = (error2) => error2 instanceof SyncRemoteMutationFenceBusyError || error2 instanceof SyncRemoteMutationFenceLostError || error2 instanceof SyncRemoteMutationFenceUnavailableError, FENCE_SCHEMA = 1, MIN_TTL_MS = 1e4, MAX_TTL_MS, MAX_FUTURE_EXPIRY_TOLERANCE_MS = 60000, MAX_RECORD_BYTES = 4096, DEFAULT_ACQUIRE_ATTEMPTS = 4, DEFAULT_HEARTBEAT_MS = 20000, ABANDONED_AFTER_MISSED_HEARTBEATS = 3, ABANDONED_JITTER_MARGIN_MS = 5000, encodeUtf8 = (text) => new TextEncoder().encode(text), decodeUtf8 = (bytes) => new TextDecoder("utf-8", { fatal: true }).decode(bytes), requireServerNow = (snapshot) => {
102646
103244
  if (snapshot.serverNowMs === null || !Number.isFinite(snapshot.serverNowMs)) {
102647
103245
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fencing requires a valid provider Date response header");
102648
103246
  }
102649
103247
  return snapshot.serverNowMs;
102650
- }, isImpossibleFutureExpiry = (expiresAt, serverNowMs) => expiresAt - serverNowMs > MAX_TTL_MS + MAX_FUTURE_EXPIRY_TOLERANCE_MS, parseRecord = (bytes) => {
103248
+ }, isImpossibleFutureExpiry = (expiresAt, serverNowMs) => expiresAt - serverNowMs > MAX_TTL_MS + MAX_FUTURE_EXPIRY_TOLERANCE_MS, isAbandonedRecord = (record3, serverNowMs) => {
103249
+ if (typeof record3.heartbeatMs !== "number" || record3.heartbeatMs <= 0)
103250
+ return false;
103251
+ if (typeof record3.renewedAt !== "number")
103252
+ return false;
103253
+ const silentForMs = serverNowMs - record3.renewedAt;
103254
+ return silentForMs > record3.heartbeatMs * ABANDONED_AFTER_MISSED_HEARTBEATS + ABANDONED_JITTER_MARGIN_MS;
103255
+ }, parseRecord = (bytes) => {
102651
103256
  if (bytes.length === 0 || bytes.length > MAX_RECORD_BYTES) {
102652
103257
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fence record is malformed");
102653
103258
  }
@@ -102664,7 +103269,14 @@ var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE
102664
103269
  if (record3.schema !== FENCE_SCHEMA || typeof record3.leaseId !== "string" || record3.leaseId.length < 8 || typeof record3.ownerId !== "string" || record3.ownerId.length < 1 || record3.purpose !== "ordinary-sync" && record3.purpose !== "encryption-transition" || typeof record3.expiresAt !== "number" || !Number.isFinite(record3.expiresAt)) {
102665
103270
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fence record is malformed");
102666
103271
  }
102667
- return record3;
103272
+ const parsedRecord = record3;
103273
+ if (typeof parsedRecord.heartbeatMs !== "number" || !Number.isFinite(parsedRecord.heartbeatMs) || parsedRecord.heartbeatMs < 0) {
103274
+ delete parsedRecord.heartbeatMs;
103275
+ }
103276
+ if (typeof parsedRecord.renewedAt !== "number" || !Number.isFinite(parsedRecord.renewedAt)) {
103277
+ delete parsedRecord.renewedAt;
103278
+ }
103279
+ return parsedRecord;
102668
103280
  }, encodeRecord = (record3) => encodeUtf8(JSON.stringify(record3)), randomLeaseId = () => {
102669
103281
  const randomUuid = globalThis.crypto?.randomUUID?.();
102670
103282
  if (randomUuid)
@@ -102674,10 +103286,12 @@ var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE
102674
103286
  var init_sync_remote_fence = __esm(() => {
102675
103287
  SyncRemoteMutationFenceBusyError = class SyncRemoteMutationFenceBusyError extends Error {
102676
103288
  retryAfterMs;
102677
- constructor(retryAfterMs) {
102678
- super("Remote sync is temporarily reserved by another compatible client");
103289
+ holder;
103290
+ constructor(retryAfterMs, holder = null) {
103291
+ super(holder ? `Remote sync is temporarily reserved by ${holder.ownerId} (${holder.purpose}, lease ${holder.leaseId}, ${Math.ceil(holder.remainingMs / 1000)}s left)` : "Remote sync is temporarily reserved by another compatible client");
102679
103292
  this.name = "SyncRemoteMutationFenceBusyError";
102680
103293
  this.retryAfterMs = Math.max(0, Math.floor(retryAfterMs));
103294
+ this.holder = holder;
102681
103295
  }
102682
103296
  };
102683
103297
  SyncRemoteMutationFenceLostError = class SyncRemoteMutationFenceLostError extends Error {
@@ -102708,6 +103322,7 @@ class SharedSyncRunMachine {
102708
103322
  performSyncCycleImpl;
102709
103323
  io = null;
102710
103324
  remoteMutationFence = null;
103325
+ carriedIdleSnapshot = takeIdleCycleSnapshot();
102711
103326
  state = {
102712
103327
  backend: "off",
102713
103328
  cloudProvider: "selfhosted",
@@ -102715,10 +103330,14 @@ class SharedSyncRunMachine {
102715
103330
  fastSyncScope: null,
102716
103331
  localSnapshotChangeAt: 0,
102717
103332
  localDataCache: null,
103333
+ localSnapshotMatchesDisk: false,
103334
+ localDocumentFingerprint: null,
103335
+ fastSyncStateCache: null,
102718
103336
  preSyncedLocalData: null,
102719
103337
  wroteLocal: false,
102720
103338
  remoteDataForCompare: null,
102721
103339
  readCheckRemoteData: undefined,
103340
+ localOnlyUploadFingerprint: null,
102722
103341
  lastRemoteWriteFingerprint: null,
102723
103342
  lastRemoteWriteMergedServerData: false,
102724
103343
  webdavRemoteCorrupted: false,
@@ -102784,10 +103403,14 @@ class SharedSyncRunMachine {
102784
103403
  await this.runAttachmentPreSyncPhase();
102785
103404
  }
102786
103405
  let skipResult = null;
103406
+ let localOnlyUpload = false;
102787
103407
  if (!this.options.activationProbe) {
102788
103408
  skipResult = await this.trySkipUnchangedFastSync();
103409
+ if (!skipResult) {
103410
+ localOnlyUpload = await this.tryArmLocalOnlyUploadFastPath();
103411
+ }
102789
103412
  }
102790
- if (!this.options.activationProbe && !skipResult && this.policy.enableReadCheckSkip) {
103413
+ if (!this.options.activationProbe && !skipResult && !localOnlyUpload && this.policy.enableReadCheckSkip) {
102791
103414
  skipResult = await this.trySkipUnchangedReadSync();
102792
103415
  }
102793
103416
  if (skipResult) {
@@ -102831,10 +103454,18 @@ class SharedSyncRunMachine {
102831
103454
  const lease = await this.runRemoteMutationFenceOperation("Remote sync mutation fence acquisition failed", acquire);
102832
103455
  if (!lease)
102833
103456
  return;
103457
+ if (lease.reclaimedFrom) {
103458
+ const from = lease.reclaimedFrom;
103459
+ this.notifier.logWarning("Reclaimed an abandoned remote sync reservation", new Error(`${from.ownerId} (${from.purpose}, lease ${from.leaseId}) stopped renewing with ${Math.ceil(from.remainingMs / 1000)}s left`));
103460
+ }
102834
103461
  this.remoteMutationFence = lease;
102835
103462
  this.state.readCheckRemoteData = undefined;
102836
103463
  this.state.remoteDataForCompare = null;
102837
103464
  }
103465
+ async acquireAndAssertRemoteMutationFence(minRemainingMs) {
103466
+ await this.ensureRemoteMutationFence();
103467
+ await this.assertRemoteMutationFenceHeld(minRemainingMs);
103468
+ }
102838
103469
  async assertRemoteMutationFenceHeld(minRemainingMs = SYNC_REMOTE_MUTATION_REQUEST_HORIZON_MS) {
102839
103470
  if (!this.remoteMutationFence)
102840
103471
  return;
@@ -102882,7 +103513,7 @@ class SharedSyncRunMachine {
102882
103513
  attachmentHelpers(phase) {
102883
103514
  return {
102884
103515
  ensureLocalSnapshotFresh: () => this.ensureLocalSnapshotFresh(),
102885
- assertRemoteMutationFenceHeld: (minRemainingMs) => this.assertRemoteMutationFenceHeld(minRemainingMs),
103516
+ assertRemoteMutationFenceHeld: (minRemainingMs) => this.acquireAndAssertRemoteMutationFence(minRemainingMs),
102886
103517
  activationProbe: this.options.activationProbe === true,
102887
103518
  phase
102888
103519
  };
@@ -102918,15 +103549,38 @@ class SharedSyncRunMachine {
102918
103549
  this.state.localSnapshotChangeAt = currentChangeAt;
102919
103550
  return this.state.localDataCache.data;
102920
103551
  }
103552
+ const carried = this.policy.carryIdleCycleSnapshot ? this.carriedIdleSnapshot : null;
103553
+ if (carried && carried.scope === this.state.fastSyncScope && carried.changeAt === currentChangeAt && !this.state.preSyncedLocalData && !this.options.activationProbe && (await this.readFastSyncState(carried.scope))?.localFingerprint === carried.fingerprint) {
103554
+ this.state.localSnapshotChangeAt = currentChangeAt;
103555
+ this.state.localDataCache = { changeAt: currentChangeAt, data: carried.data };
103556
+ this.state.localSnapshotMatchesDisk = true;
103557
+ this.state.localDocumentFingerprint = { data: carried.data, fingerprint: carried.fingerprint };
103558
+ this.notifier.logInfo("Sync local reconcile", {
103559
+ reconcile: "idle-cache",
103560
+ durationMs: "0",
103561
+ tasks: String(carried.data.tasks.length)
103562
+ });
103563
+ return carried.data;
103564
+ }
102921
103565
  const inMemorySnapshot = this.store.getInMemorySnapshot();
102922
103566
  let baseData;
103567
+ let matchesDisk = false;
102923
103568
  if (this.state.preSyncedLocalData) {
102924
- baseData = mergeAppData(this.state.preSyncedLocalData, inMemorySnapshot);
103569
+ const preSynced = this.state.preSyncedLocalData;
103570
+ const reconcileStart = Date.now();
103571
+ const aligned = computeSyncChangeFingerprint(preSynced) === computeSyncChangeFingerprint(inMemorySnapshot) && computeAttachmentIdentityDigest(preSynced) === computeAttachmentIdentityDigest(inMemorySnapshot);
103572
+ baseData = aligned ? preSynced : mergeAppData(preSynced, inMemorySnapshot);
103573
+ this.notifier.logInfo("Sync local reconcile", {
103574
+ reconcile: aligned ? "aligned-skip-presynced" : "merged-presynced",
103575
+ durationMs: String(Date.now() - reconcileStart),
103576
+ tasks: String(baseData.tasks.length)
103577
+ });
102925
103578
  } else {
102926
103579
  const persisted = await this.storage.readPersistedLocal();
102927
103580
  const reconcileStart = Date.now();
102928
103581
  const aligned = computeSyncChangeFingerprint(persisted) === computeSyncChangeFingerprint(inMemorySnapshot);
102929
103582
  baseData = aligned ? persisted : mergeAppData(persisted, inMemorySnapshot);
103583
+ matchesDisk = aligned;
102930
103584
  this.notifier.logInfo("Sync local reconcile", {
102931
103585
  reconcile: aligned ? "aligned-skip" : "merged",
102932
103586
  durationMs: String(Date.now() - reconcileStart),
@@ -102939,12 +103593,14 @@ class SharedSyncRunMachine {
102939
103593
  changeAt: currentChangeAt,
102940
103594
  data
102941
103595
  };
103596
+ this.state.localSnapshotMatchesDisk = matchesDisk && data === baseData;
102942
103597
  return data;
102943
103598
  }
102944
103599
  async persistLocalDataWithTracking(data) {
102945
103600
  await this.assertRemoteMutationFenceHeld();
102946
103601
  const persisted = await this.storage.persistLocal(data) ?? data;
102947
103602
  this.ensureLocalSnapshotFresh(persisted);
103603
+ this.state.localSnapshotMatchesDisk = false;
102948
103604
  if (this.storage.applyDataToStore) {
102949
103605
  this.storage.applyDataToStore(persisted);
102950
103606
  const currentChangeAt = this.store.getLastDataChangeAt();
@@ -102975,6 +103631,10 @@ class SharedSyncRunMachine {
102975
103631
  this.state.remoteDataForCompare = data;
102976
103632
  return data;
102977
103633
  }
103634
+ if (this.state.localOnlyUploadFingerprint) {
103635
+ this.state.remoteDataForCompare = null;
103636
+ return null;
103637
+ }
102978
103638
  await this.ensureNetwork();
102979
103639
  try {
102980
103640
  const raw = await this.requireIo().readRemote();
@@ -103005,7 +103665,23 @@ class SharedSyncRunMachine {
103005
103665
  return null;
103006
103666
  return io.readRemoteFingerprint();
103007
103667
  }
103668
+ assertLocalOnlyUploadIsCanonical(data) {
103669
+ if (!this.state.localOnlyUploadFingerprint)
103670
+ return;
103671
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "development")
103672
+ return;
103673
+ const parsedEmpty = parseSyncDocument({}, "remote");
103674
+ const emptyRemote = parsedEmpty.ok ? parsedEmpty.data : { tasks: [], projects: [], sections: [], areas: [], people: [], settings: {} };
103675
+ const asWritten = toStableSyncJson(toRemoteSyncDocument(data));
103676
+ const asMerged = toStableSyncJson(toRemoteSyncDocument(mergeAppData(data, emptyRemote)));
103677
+ if (asWritten === asMerged)
103678
+ return;
103679
+ const message3 = "Sync canonical-read invariant broken: the local-only upload fast path " + "is about to publish a document the normalize pass would still change. A local " + "write path is producing a non-canonical entity; see " + "sync-canonical-reads.contract.test.ts.";
103680
+ this.notifier.logWarning(message3);
103681
+ throw new Error(message3);
103682
+ }
103008
103683
  async writeRemoteForCycle(data) {
103684
+ this.assertLocalOnlyUploadIsCanonical(data);
103009
103685
  await this.ensureRemoteMutationFence();
103010
103686
  await this.assertRemoteMutationFenceHeld();
103011
103687
  await this.ensureNetwork();
@@ -103039,7 +103715,7 @@ class SharedSyncRunMachine {
103039
103715
  } catch (error2) {
103040
103716
  if (error2 instanceof SyncRemoteWriteConflict) {
103041
103717
  this.requestFollowUp();
103042
- throw new LocalSyncAbort;
103718
+ throw new LocalSyncAbort("remote-write-conflict");
103043
103719
  }
103044
103720
  throw error2;
103045
103721
  }
@@ -103089,8 +103765,8 @@ class SharedSyncRunMachine {
103089
103765
  this.ensureLocalSnapshotFresh();
103090
103766
  if (hasPendingSyncSideEffects(localData))
103091
103767
  return null;
103092
- const localFingerprint = computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(localData));
103093
- const cached2 = await this.storage.readFastSyncState(scope);
103768
+ const localFingerprint = this.localDocumentFingerprint(localData);
103769
+ const cached2 = await this.readFastSyncState(scope);
103094
103770
  if (!cached2 || cached2.localFingerprint !== localFingerprint)
103095
103771
  return null;
103096
103772
  let remoteFingerprint = null;
@@ -103103,16 +103779,64 @@ class SharedSyncRunMachine {
103103
103779
  if (!remoteFingerprint || remoteFingerprint !== cached2.remoteFingerprint)
103104
103780
  return null;
103105
103781
  this.ensureLocalSnapshotFresh();
103106
- await this.storage.writeFastSyncState({
103782
+ await this.writeFastSyncState({
103107
103783
  scope,
103108
103784
  localFingerprint,
103109
103785
  remoteFingerprint,
103110
103786
  checkedAt: this.nowIso()
103111
103787
  });
103112
103788
  await this.persistUnchangedSyncStatus();
103789
+ this.publishIdleCycleSnapshot(localData, localFingerprint);
103113
103790
  this.notifier.logInfo("Sync fast check found no changes", { backend: this.backend });
103791
+ this.logUnchangedCycleSkips("fast");
103114
103792
  return { success: true, skipped: "unchanged" };
103115
103793
  }
103794
+ async tryArmLocalOnlyUploadFastPath() {
103795
+ if (this.options.manual)
103796
+ return false;
103797
+ if (this.options.ignorePendingRemoteWriteBackoff)
103798
+ return false;
103799
+ const scope = this.state.fastSyncScope;
103800
+ if (!scope)
103801
+ return false;
103802
+ const io = this.requireIo();
103803
+ if (!io.adoptRemoteFingerprintForWrite || !io.readRemoteFingerprint)
103804
+ return false;
103805
+ if (io.requiresRemoteRepair?.() === true)
103806
+ return false;
103807
+ if (!this.policy.preSyncAttachmentsBeforeFastCheck)
103808
+ return false;
103809
+ this.setStep("fast-check");
103810
+ await this.yieldToUi();
103811
+ if (this.state.preSyncedLocalData)
103812
+ return false;
103813
+ const localData = await this.readLocalDataForSyncCycle();
103814
+ this.ensureLocalSnapshotFresh();
103815
+ if (hasPendingSyncSideEffects(localData))
103816
+ return false;
103817
+ const cached2 = await this.readFastSyncState(scope);
103818
+ if (!cached2)
103819
+ return false;
103820
+ if (cached2.localFingerprint === this.localDocumentFingerprint(localData))
103821
+ return false;
103822
+ let remoteFingerprint = null;
103823
+ try {
103824
+ remoteFingerprint = await this.readRemoteFingerprint();
103825
+ } catch (error2) {
103826
+ this.notifier.logWarning("Sync fast check failed; falling back to full sync", error2);
103827
+ return false;
103828
+ }
103829
+ if (!remoteFingerprint || remoteFingerprint !== cached2.remoteFingerprint)
103830
+ return false;
103831
+ if (!io.adoptRemoteFingerprintForWrite(remoteFingerprint))
103832
+ return false;
103833
+ this.ensureLocalSnapshotFresh();
103834
+ this.state.localOnlyUploadFingerprint = remoteFingerprint;
103835
+ this.notifier.logInfo("Sync uploading a local-only change without a remote read", {
103836
+ backend: this.backend
103837
+ });
103838
+ return true;
103839
+ }
103116
103840
  async trySkipUnchangedReadSync() {
103117
103841
  this.setStep("read-check");
103118
103842
  await this.yieldToUi();
@@ -103129,14 +103853,16 @@ class SharedSyncRunMachine {
103129
103853
  this.state.readCheckRemoteData = remoteData;
103130
103854
  if (hasUncompactedPurgedTombstones(remoteData))
103131
103855
  return null;
103132
- const localDocument = toRemoteSyncDocument(localData);
103133
- const remoteDocument = toRemoteSyncDocument(remoteData);
103134
- if (!areRemoteSyncDocumentsEqual(remoteDocument, localDocument))
103856
+ const localFingerprint = this.localDocumentFingerprint(localData);
103857
+ const remoteFingerprint = computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(remoteData));
103858
+ if (localFingerprint !== remoteFingerprint)
103135
103859
  return null;
103136
103860
  await this.recordFastSyncState(localData, { allowRemoteFingerprintRead: false });
103137
103861
  await this.persistUnchangedSyncStatus();
103862
+ this.publishIdleCycleSnapshot(localData, localFingerprint);
103138
103863
  this.state.readCheckRemoteData = undefined;
103139
103864
  this.notifier.logInfo("Sync read check found no changes", { backend: this.backend });
103865
+ this.logUnchangedCycleSkips("read");
103140
103866
  return { success: true, skipped: "unchanged" };
103141
103867
  }
103142
103868
  async recordFastSyncState(data, options = {}) {
@@ -103163,13 +103889,71 @@ class SharedSyncRunMachine {
103163
103889
  }
103164
103890
  if (!remoteFingerprint)
103165
103891
  return;
103166
- await this.storage.writeFastSyncState({
103892
+ await this.writeFastSyncState({
103167
103893
  scope,
103168
- localFingerprint: computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(data)),
103894
+ localFingerprint: this.localDocumentFingerprint(data),
103169
103895
  remoteFingerprint,
103170
103896
  checkedAt: this.nowIso()
103171
103897
  });
103172
103898
  }
103899
+ async readFastSyncState(scope) {
103900
+ const cached2 = this.state.fastSyncStateCache;
103901
+ if (cached2 && cached2.scope === scope)
103902
+ return cached2.value;
103903
+ const value = await this.storage.readFastSyncState(scope);
103904
+ this.state.fastSyncStateCache = { scope, value };
103905
+ return value;
103906
+ }
103907
+ async writeFastSyncState(state) {
103908
+ this.state.fastSyncStateCache = { scope: state.scope, value: state };
103909
+ await this.storage.writeFastSyncState(state);
103910
+ }
103911
+ localDocumentFingerprint(data) {
103912
+ const cached2 = this.state.localDocumentFingerprint;
103913
+ if (cached2 && cached2.data === data)
103914
+ return cached2.fingerprint;
103915
+ const fingerprint = computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(data));
103916
+ this.state.localDocumentFingerprint = { data, fingerprint };
103917
+ return fingerprint;
103918
+ }
103919
+ isLocalPersistUnchanged(data) {
103920
+ if (this.options.activationProbe)
103921
+ return false;
103922
+ if (!this.state.localSnapshotMatchesDisk)
103923
+ return false;
103924
+ const stored = this.state.localDataCache?.data;
103925
+ if (!stored)
103926
+ return false;
103927
+ return isLocalPersistEquivalent(data, stored);
103928
+ }
103929
+ logUnchangedCycleSkips(check2) {
103930
+ const skippedPasses = ["local-persist", "store-refresh", "attachments"];
103931
+ if (!this.remoteMutationFence)
103932
+ skippedPasses.push("remote-fence");
103933
+ this.notifier.logInfo("Sync cycle changed nothing; passes skipped", {
103934
+ releaseCheck: "v1.2.7/cycle-unchanged-skip",
103935
+ backend: this.backend,
103936
+ check: check2,
103937
+ skipped: skippedPasses.join(",")
103938
+ });
103939
+ }
103940
+ publishIdleCycleSnapshot(data, fingerprint) {
103941
+ const scope = this.state.fastSyncScope;
103942
+ if (!this.policy.carryIdleCycleSnapshot)
103943
+ return;
103944
+ if (!scope || this.options.activationProbe)
103945
+ return;
103946
+ if (!this.state.localSnapshotMatchesDisk || this.state.wroteLocal)
103947
+ return;
103948
+ if (this.store.getLastDataChangeAt() !== this.state.localSnapshotChangeAt)
103949
+ return;
103950
+ idleCycleSnapshot = {
103951
+ scope,
103952
+ changeAt: this.state.localSnapshotChangeAt,
103953
+ data,
103954
+ fingerprint
103955
+ };
103956
+ }
103173
103957
  async runAttachmentPreSyncPhase() {
103174
103958
  if (!this.policy.attachmentPhasesEnabled || this.options.activationProbe)
103175
103959
  return;
@@ -103186,7 +103970,6 @@ class SharedSyncRunMachine {
103186
103970
  if (isRemoteSyncBackend(this.backend)) {
103187
103971
  await this.ensureNetwork();
103188
103972
  }
103189
- await this.ensureRemoteMutationFence();
103190
103973
  const result = await io.syncAttachments(localData, this.attachmentHelpers("prepare"));
103191
103974
  await this.assertRemoteMutationFenceHeld();
103192
103975
  const mutated = result === true || Boolean(result) && typeof result === "object";
@@ -103241,7 +104024,7 @@ class SharedSyncRunMachine {
103241
104024
  await this.assertRemoteMutationFenceHeld();
103242
104025
  provenData = result2 && typeof result2 === "object" ? result2 : fallbackRetry.data;
103243
104026
  }
103244
- assertActivationAttachmentsProven(provenData, activationSnapshot.expectedIds);
104027
+ assertActivationAttachmentsProven(provenData, activationSnapshot.expectedIds, activationSnapshot.metadataOnlyIds, activationSnapshot.noLocalBytesIds);
103245
104028
  this.ensureLocalSnapshotFresh();
103246
104029
  this.notifier.onDiagnostic?.({
103247
104030
  event: "attachments-prepare-complete",
@@ -103373,6 +104156,19 @@ class SharedSyncRunMachine {
103373
104156
  });
103374
104157
  },
103375
104158
  flushPendingLocalBeforeRetryRead: () => this.options.activationProbe ? Promise.resolve() : this.store.flushPendingSave(),
104159
+ isLocalPersistUnchanged: (data) => this.isLocalPersistUnchanged(data),
104160
+ persistSyncStatusOnly: async (data) => {
104161
+ this.notifier.logInfo("Sync local write skipped; merged document matches stored", {
104162
+ backend: this.backend
104163
+ });
104164
+ await this.storage.persistSyncStatus({
104165
+ lastSyncAt: data.settings.lastSyncAt,
104166
+ lastSyncStatus: data.settings.lastSyncStatus,
104167
+ lastSyncError: data.settings.lastSyncError,
104168
+ lastSyncStats: data.settings.lastSyncStats,
104169
+ lastSyncHistory: data.settings.lastSyncHistory
104170
+ });
104171
+ },
103376
104172
  prepareRemoteWrite: (data) => this.prepareRemoteWriteData(data),
103377
104173
  writeRemote: async (data) => {
103378
104174
  this.notifier.tracePayload?.("write-remote", data, { backend: this.backend });
@@ -103380,6 +104176,7 @@ class SharedSyncRunMachine {
103380
104176
  await this.writeRemoteForCycle(data);
103381
104177
  },
103382
104178
  preferIncomingAttachmentCloudKeys: this.options.ignorePendingRemoteWriteBackoff === true,
104179
+ skipEmptyRemoteMerge: () => this.state.localOnlyUploadFingerprint !== null,
103383
104180
  onStep: (next) => this.setStep(next),
103384
104181
  yieldToUi: this.notifier.yieldToUi ? () => this.notifier.yieldToUi() : undefined,
103385
104182
  historyContext: {
@@ -103466,9 +104263,11 @@ class SharedSyncRunMachine {
103466
104263
  await this.yieldToUi();
103467
104264
  this.ensureLocalSnapshotFresh(mergedData);
103468
104265
  await this.assertRemoteMutationFenceHeld();
104266
+ const localWriteSkipped = syncResult.localWriteSkipped && !this.state.wroteLocal ? true : undefined;
103469
104267
  await this.hooks.finalizeSuccess(mergedData, {
103470
104268
  status: syncResult.status,
103471
104269
  wroteLocal: this.state.wroteLocal,
104270
+ localWriteSkipped,
103472
104271
  getLocalSnapshotChangeAt: () => this.state.localSnapshotChangeAt,
103473
104272
  acceptCoveredSnapshot: (expectedData) => this.acceptCoveredLocalSnapshot(expectedData)
103474
104273
  });
@@ -103480,6 +104279,7 @@ class SharedSyncRunMachine {
103480
104279
  attachmentWriteDeferred: attachmentWriteDeferred || undefined,
103481
104280
  fileAttachmentUploadBlocked: this.state.fileAttachmentUploadBlocked ?? undefined,
103482
104281
  error: mergedData.settings.lastSyncError,
104282
+ localWriteSkipped,
103483
104283
  stats
103484
104284
  };
103485
104285
  }
@@ -103487,6 +104287,7 @@ class SharedSyncRunMachine {
103487
104287
  success: true,
103488
104288
  attachmentWriteDeferred: attachmentWriteDeferred || undefined,
103489
104289
  fileAttachmentUploadBlocked: this.state.fileAttachmentUploadBlocked ?? undefined,
104290
+ localWriteSkipped,
103490
104291
  stats
103491
104292
  };
103492
104293
  }
@@ -103512,6 +104313,7 @@ class SharedSyncRunMachine {
103512
104313
  };
103513
104314
  }
103514
104315
  if (error2 instanceof SyncRemoteMutationFenceBusyError) {
104316
+ this.notifier.logWarning("Remote sync location is reserved; retrying after the lease lapses", error2);
103515
104317
  if (!this.options.activationProbe)
103516
104318
  this.requestFollowUpAfter(error2.retryAfterMs);
103517
104319
  return {
@@ -103533,6 +104335,12 @@ class SharedSyncRunMachine {
103533
104335
  if (!this.options.activationProbe) {
103534
104336
  await this.persistPreSyncedDataAfterAbort();
103535
104337
  }
104338
+ this.notifier.logInfo("Sync cycle requeued", {
104339
+ backend: this.backend,
104340
+ step: this.state.step ?? "-",
104341
+ reason: error2.reason,
104342
+ activationProbe: String(this.options.activationProbe === true)
104343
+ });
103536
104344
  this.notifier.onDiagnostic?.({
103537
104345
  event: "requeued",
103538
104346
  extra: {
@@ -103615,7 +104423,28 @@ var normalizeRemoteWriteResult = (source, result) => {
103615
104423
  fingerprint,
103616
104424
  serverMergedRemoteData: result.serverMergedRemoteData === true
103617
104425
  };
103618
- }, DEFAULT_CLEANUP_INTERVAL_MS, withoutInheritedPendingRemoteWrite = (data) => {
104426
+ }, DEFAULT_CLEANUP_INTERVAL_MS, computeAttachmentIdentityDigest = (data) => {
104427
+ const parts = [];
104428
+ const append = (items) => {
104429
+ for (const item of items ?? []) {
104430
+ const attachments = item.attachments;
104431
+ if (!attachments || attachments.length === 0)
104432
+ continue;
104433
+ const ordered = [...attachments].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
104434
+ parts.push(`${item.id}:${toStableSyncJson(ordered)}`);
104435
+ }
104436
+ };
104437
+ append(data.tasks);
104438
+ append(data.projects);
104439
+ return parts.join(`
104440
+ `);
104441
+ }, idleCycleSnapshot = null, takeIdleCycleSnapshot = () => {
104442
+ const carried = idleCycleSnapshot;
104443
+ idleCycleSnapshot = null;
104444
+ return carried;
104445
+ }, clearIdleSyncCycleSnapshot = () => {
104446
+ idleCycleSnapshot = null;
104447
+ }, withoutInheritedPendingRemoteWrite = (data) => {
103619
104448
  if (!data.settings.pendingRemoteWriteAt && data.settings.pendingRemoteWriteRetryAt === undefined && data.settings.pendingRemoteWriteAttempts === undefined) {
103620
104449
  return data;
103621
104450
  }
@@ -103696,9 +104525,17 @@ var normalizeRemoteWriteResult = (source, result) => {
103696
104525
  const candidate = cloneAppData(data);
103697
104526
  const expectedIds = new Set;
103698
104527
  const localFallbacks = new Map;
104528
+ const metadataOnlyIds = new Set;
104529
+ const noLocalBytesIds = new Set;
103699
104530
  const count = visitLiveFileAttachments(candidate, (attachment) => {
103700
104531
  expectedIds.add(attachment.id);
103701
104532
  const candidateAttachment = candidateAttachments.get(attachment.id);
104533
+ const localAttachment = localAttachments.get(attachment.id);
104534
+ const noLocalBytes = !localAttachment?.cloudKey && (!localAttachment || !localAttachment.uri?.trim() || localAttachment.localStatus === "missing");
104535
+ if (noLocalBytes)
104536
+ noLocalBytesIds.add(attachment.id);
104537
+ if (noLocalBytes && !candidateAttachment?.cloudKey)
104538
+ metadataOnlyIds.add(attachment.id);
103702
104539
  const mustReplaceCandidateBlob = Boolean(candidateAttachment?.cloudKey && mergedContentMustReplaceCandidateBlob(attachment, candidateAttachment));
103703
104540
  if (!mustReplaceCandidateBlob) {
103704
104541
  const fallback = buildExactLocalActivationFallback(attachment, localAttachments.get(attachment.id), candidateAttachment);
@@ -103712,7 +104549,7 @@ var normalizeRemoteWriteResult = (source, result) => {
103712
104549
  }
103713
104550
  attachment.localStatus = "missing";
103714
104551
  });
103715
- return { data: candidate, count, expectedIds, localFallbacks };
104552
+ return { data: candidate, count, expectedIds, localFallbacks, metadataOnlyIds, noLocalBytesIds };
103716
104553
  }, prepareActivationFallbackRetry = (data, localFallbacks) => {
103717
104554
  if (localFallbacks.size === 0)
103718
104555
  return { data, count: 0 };
@@ -103732,7 +104569,7 @@ var normalizeRemoteWriteResult = (source, result) => {
103732
104569
  }
103733
104570
  }
103734
104571
  return count > 0 ? { data: retryData, count } : { data, count: 0 };
103735
- }, assertActivationAttachmentsProven = (data, expectedIds) => {
104572
+ }, assertActivationAttachmentsProven = (data, expectedIds, metadataOnlyIds, noLocalBytesIds) => {
103736
104573
  const resolved = new Set;
103737
104574
  for (const owner of [...data.tasks, ...data.projects]) {
103738
104575
  for (const attachment of owner.attachments ?? []) {
@@ -103740,10 +104577,18 @@ var normalizeRemoteWriteResult = (source, result) => {
103740
104577
  continue;
103741
104578
  if (owner.deletedAt || attachment.deletedAt) {
103742
104579
  if (expectedIds.has(attachment.id)) {
103743
- throw new Error(`Candidate attachment proof failed for ${attachment.id}`);
104580
+ if (!noLocalBytesIds.has(attachment.id)) {
104581
+ throw new Error(`Candidate attachment proof failed for ${attachment.id}`);
104582
+ }
104583
+ resolved.add(attachment.id);
103744
104584
  }
103745
104585
  continue;
103746
104586
  }
104587
+ if (metadataOnlyIds.has(attachment.id) && !attachment.cloudKey && attachment.localStatus === "missing" && attachment.pendingContentUpload !== true) {
104588
+ if (expectedIds.has(attachment.id))
104589
+ resolved.add(attachment.id);
104590
+ continue;
104591
+ }
103747
104592
  if (!attachment.cloudKey || attachment.localStatus !== "available" || attachment.pendingContentUpload === true) {
103748
104593
  throw new Error(`Candidate attachment proof failed for ${attachment.id}`);
103749
104594
  }
@@ -104416,8 +105261,19 @@ var DROPBOX_SYNC_PATH = "/data.json", DOWNLOAD_ENDPOINT = "https://content.dropb
104416
105261
  throw new Error(`Dropbox download failed: HTTP ${response.status}`);
104417
105262
  }
104418
105263
  return "ok";
105264
+ }, DROPBOX_ATTACHMENTS_PATH, createDropboxAttachmentPresenceIndex = (entries) => {
105265
+ const names = new Set(entries.map((entry) => entry.name.toLowerCase()));
105266
+ return (cloudKey) => {
105267
+ const segments = cloudKey.split("/");
105268
+ if (segments.length !== 2)
105269
+ return null;
105270
+ if (segments[0] !== ATTACHMENTS_DIR_NAME || !segments[1])
105271
+ return null;
105272
+ return names.has(segments[1].toLowerCase());
105273
+ };
104419
105274
  };
104420
105275
  var init_dropbox = __esm(() => {
105276
+ init_attachment_paths();
104421
105277
  init_dropbox_sync_utils();
104422
105278
  init_http_utils();
104423
105279
  init_sync_encryption();
@@ -104441,6 +105297,7 @@ var init_dropbox = __esm(() => {
104441
105297
  this.name = "DropboxFileNotFoundError";
104442
105298
  }
104443
105299
  };
105300
+ DROPBOX_ATTACHMENTS_PATH = `/${ATTACHMENTS_DIR_NAME}`;
104444
105301
  });
104445
105302
 
104446
105303
  // ../../packages/core/src/sync-backend-io.ts
@@ -104484,6 +105341,28 @@ function createSyncBackendIO(ctx, transport) {
104484
105341
  },
104485
105342
  getSyncUrl: () => ctx.syncUrl,
104486
105343
  getCachedRemoteFingerprint: () => ctx.backend === "cloud" && ctx.cloudProvider === "dropbox" && ctx.dropboxRev ? buildDropboxRevFingerprint(ctx.dropboxRev) : null,
105344
+ adoptRemoteFingerprintForWrite: (fingerprint) => {
105345
+ if (ctx.backend === "webdav") {
105346
+ if (ctx.allowLegacyWebdavPlaintext)
105347
+ return false;
105348
+ const strongEtag = strongEtagFromWebdavFingerprint(fingerprint);
105349
+ if (!strongEtag)
105350
+ return false;
105351
+ webdavDocumentVersion = { exists: true, strongEtag };
105352
+ webdavDocumentSnapshot = null;
105353
+ return true;
105354
+ }
105355
+ if (ctx.backend === "cloud" && ctx.cloudProvider === "dropbox") {
105356
+ if (!fingerprint.startsWith(DROPBOX_REV_FINGERPRINT_PREFIX))
105357
+ return false;
105358
+ const rev = fingerprint.slice(DROPBOX_REV_FINGERPRINT_PREFIX.length);
105359
+ if (!rev)
105360
+ return false;
105361
+ ctx.dropboxRev = rev;
105362
+ return true;
105363
+ }
105364
+ return false;
105365
+ },
104487
105366
  readRemote: async () => {
104488
105367
  if (ctx.backend === "cloudkit") {
104489
105368
  return transport.cloudKitRead();
@@ -104497,6 +105376,9 @@ function createSyncBackendIO(ctx, transport) {
104497
105376
  const remote2 = await transport.webdavGet();
104498
105377
  webdavDocumentVersion = { exists: remote2.exists, strongEtag: remote2.strongEtag };
104499
105378
  webdavDocumentSnapshot = snapshotWebdavRead(remote2);
105379
+ if (ctx.syncEncryptionOff && !ctx.allowLegacyWebdavPlaintext && remote2.exists && !remote2.strongEtag) {
105380
+ ctx.allowLegacyWebdavPlaintext = true;
105381
+ }
104500
105382
  return remote2.data;
104501
105383
  } catch (error2) {
104502
105384
  webdavDocumentVersion = getWebdavDocumentVersionFromError(error2);
@@ -104651,7 +105533,7 @@ function createSyncBackendIO(ctx, transport) {
104651
105533
  }
104652
105534
  };
104653
105535
  }
104654
- var DROPBOX_REV_FINGERPRINT_PREFIX = "dropbox:v1:rev=", buildDropboxRevFingerprint = (rev) => `${DROPBOX_REV_FINGERPRINT_PREFIX}${rev}`, isFileSyncReadResult = (value) => typeof value === "object" && value !== null && ("data" in value) && ("fingerprint" in value) && typeof value.fingerprint === "string";
105536
+ var DROPBOX_REV_FINGERPRINT_PREFIX = "dropbox:v1:rev=", WEBDAV_ETAG_FINGERPRINT_PREFIX = "webdav:v1:etag=", buildDropboxRevFingerprint = (rev) => `${DROPBOX_REV_FINGERPRINT_PREFIX}${rev}`, strongEtagFromWebdavFingerprint = (fingerprint) => fingerprint.startsWith(WEBDAV_ETAG_FINGERPRINT_PREFIX) ? normalizeStrongWebdavEtag(fingerprint.slice(WEBDAV_ETAG_FINGERPRINT_PREFIX.length)) : null, isFileSyncReadResult = (value) => typeof value === "object" && value !== null && ("data" in value) && ("fingerprint" in value) && typeof value.fingerprint === "string";
104655
105537
  var init_sync_backend_io = __esm(() => {
104656
105538
  init_dropbox();
104657
105539
  init_sync_helpers();
@@ -104704,14 +105586,162 @@ var init_sync_fast_sync = __esm(() => {
104704
105586
  init_sync_helpers();
104705
105587
  });
104706
105588
 
105589
+ // ../../packages/core/src/sync-encryption-diagnostics.ts
105590
+ var SYNC_ENCRYPTION_LOG_PREFIX = "[sync-encryption]", SYNC_ENCRYPTION_LOG_EVENTS, syncEncryptionLogMessage = (event) => `${SYNC_ENCRYPTION_LOG_PREFIX} ${event}`, SYNC_ENCRYPTION_LOG_ABSENT = "-", MAX_LOGGED_MESSAGE_CHARS = 200, HEX_ONLY, finalize = (fields) => sanitizeLogContext(fields) ?? {}, flag = (value) => value === null || value === undefined ? SYNC_ENCRYPTION_LOG_ABSENT : String(value === true), syncEncryptionSaltPrefix = (salt) => {
105591
+ if (!salt)
105592
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105593
+ if (typeof salt === "string") {
105594
+ const trimmed = salt.trim();
105595
+ if (!trimmed || !HEX_ONLY.test(trimmed))
105596
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105597
+ return trimmed.slice(0, 8).toLowerCase();
105598
+ }
105599
+ if (salt.length === 0)
105600
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105601
+ let out = "";
105602
+ for (let index = 0;index < salt.length && out.length < 8; index += 1) {
105603
+ out += salt[index].toString(16).padStart(2, "0");
105604
+ }
105605
+ return out.slice(0, 8);
105606
+ }, syncEncryptionKdfLabel = (params) => {
105607
+ if (!params)
105608
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105609
+ return `m=${params.mKib},t=${params.t},p=${params.p}`;
105610
+ }, digest32 = (value) => {
105611
+ let hash = 2166136261;
105612
+ for (let index = 0;index < value.length; index += 1) {
105613
+ hash ^= value.charCodeAt(index);
105614
+ hash = Math.imul(hash, 16777619) >>> 0;
105615
+ }
105616
+ return hash.toString(16).padStart(8, "0");
105617
+ }, syncEncryptionScopeLabel = (scope) => {
105618
+ if (!scope)
105619
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105620
+ let backend = "scope";
105621
+ try {
105622
+ const parsed = JSON.parse(scope);
105623
+ if (Array.isArray(parsed) && typeof parsed[0] === "string" && parsed[0]) {
105624
+ backend = parsed[0];
105625
+ }
105626
+ } catch {}
105627
+ return `${backend}#${digest32(scope)}`;
105628
+ }, syncEncryptionArtifactLabel = (name) => {
105629
+ if (!name)
105630
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105631
+ const stripped = name.split("?")[0].split("#")[0].replace(/[\\/]+$/, "");
105632
+ if (!stripped)
105633
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105634
+ const lastSeparator = Math.max(stripped.lastIndexOf("/"), stripped.lastIndexOf("\\"));
105635
+ const leaf = lastSeparator >= 0 ? stripped.slice(lastSeparator + 1) : stripped;
105636
+ return leaf || SYNC_ENCRYPTION_LOG_ABSENT;
105637
+ }, URL_IN_MESSAGE, clampMessage = (message3) => {
105638
+ const trimmed = message3?.trim();
105639
+ if (!trimmed)
105640
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105641
+ const safe = sanitizeForLog(trimmed.replace(URL_IN_MESSAGE, (url) => sanitizeUrl(url) ?? "[redacted]"));
105642
+ return safe.length > MAX_LOGGED_MESSAGE_CHARS ? `${safe.slice(0, MAX_LOGGED_MESSAGE_CHARS)}…` : safe;
105643
+ }, buildSyncEncryptionStateExtra = (input) => finalize({
105644
+ backend: input.backend || SYNC_ENCRYPTION_LOG_ABSENT,
105645
+ trigger: input.trigger,
105646
+ state: input.state,
105647
+ hasMaterial: flag(input.hasMaterial),
105648
+ saltPrefix: syncEncryptionSaltPrefix(input.salt),
105649
+ kdf: syncEncryptionKdfLabel(input.kdf),
105650
+ incompleteTransition: input.incompleteTransition ?? SYNC_ENCRYPTION_LOG_ABSENT,
105651
+ discoveredScope: input.discoveredScopeLabel?.trim() || syncEncryptionScopeLabel(input.discoveredScope),
105652
+ activeScope: syncEncryptionScopeLabel(input.activeScope),
105653
+ decision: input.decision
105654
+ }), buildSyncEncryptionRemoteReadExtra = (input) => finalize({
105655
+ artifact: syncEncryptionArtifactLabel(input.artifact),
105656
+ exists: flag(input.exists),
105657
+ kind: input.kind,
105658
+ headerSaltPrefix: syncEncryptionSaltPrefix(input.headerSalt),
105659
+ headerKdf: syncEncryptionKdfLabel(input.headerKdf),
105660
+ bytes: input.bytes === null || input.bytes === undefined ? SYNC_ENCRYPTION_LOG_ABSENT : String(input.bytes),
105661
+ version: input.version ?? SYNC_ENCRYPTION_LOG_ABSENT,
105662
+ foreignSalt: flag(input.foreignSalt),
105663
+ decision: input.decision
105664
+ }), buildSyncEncryptionTransitionExtra = (input) => finalize({
105665
+ kind: input.kind,
105666
+ backend: input.backend || SYNC_ENCRYPTION_LOG_ABSENT,
105667
+ phase: input.phase,
105668
+ artifact: input.artifact ? syncEncryptionArtifactLabel(input.artifact) : SYNC_ENCRYPTION_LOG_ABSENT,
105669
+ planned: input.planned === null || input.planned === undefined ? SYNC_ENCRYPTION_LOG_ABSENT : String(input.planned),
105670
+ done: input.done === null || input.done === undefined ? SYNC_ENCRYPTION_LOG_ABSENT : String(input.done),
105671
+ outcome: input.outcome ?? SYNC_ENCRYPTION_LOG_ABSENT,
105672
+ errorName: input.errorName || SYNC_ENCRYPTION_LOG_ABSENT,
105673
+ errorMessage: input.errorName ? clampMessage(input.errorMessage) : SYNC_ENCRYPTION_LOG_ABSENT
105674
+ }), SYNC_ENCRYPTION_LOG_SENTINELS, findSyncEncryptionSentinel = (message3) => {
105675
+ if (!message3)
105676
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105677
+ for (const sentinel of [...SYNC_ENCRYPTION_LOG_SENTINELS].sort((a, b) => b.length - a.length)) {
105678
+ if (message3.includes(sentinel))
105679
+ return sentinel;
105680
+ }
105681
+ return SYNC_ENCRYPTION_LOG_ABSENT;
105682
+ }, lastSyncEncryptionError = null, getLastSyncEncryptionError = () => lastSyncEncryptionError, resetLastSyncEncryptionError = () => {
105683
+ lastSyncEncryptionError = null;
105684
+ }, buildSyncEncryptionErrorExtra = (input) => {
105685
+ const at = input.at ?? new Date().toISOString();
105686
+ lastSyncEncryptionError = { name: input.errorName, at };
105687
+ return finalize({
105688
+ errorName: input.errorName || SYNC_ENCRYPTION_LOG_ABSENT,
105689
+ sentinel: findSyncEncryptionSentinel(input.errorMessage),
105690
+ backend: input.backend || SYNC_ENCRYPTION_LOG_ABSENT,
105691
+ step: input.step || SYNC_ENCRYPTION_LOG_ABSENT,
105692
+ classification: input.classification || SYNC_ENCRYPTION_LOG_ABSENT,
105693
+ errorMessage: clampMessage(input.errorMessage)
105694
+ });
105695
+ }, buildSyncEncryptionActivationExtra = (input) => finalize({
105696
+ activationProof: input.activationProof ?? SYNC_ENCRYPTION_LOG_ABSENT,
105697
+ stateBefore: input.stateBefore,
105698
+ stateAfter: input.stateAfter,
105699
+ backend: input.backend || SYNC_ENCRYPTION_LOG_ABSENT
105700
+ }), formatSyncEncryptionDiagnostics = (input) => {
105701
+ const lastError = getLastSyncEncryptionError();
105702
+ return [
105703
+ `state: ${input.state}`,
105704
+ `location: ${syncEncryptionScopeLabel(input.activeScope)}`,
105705
+ `material: ${flag(input.hasMaterial)}`,
105706
+ `salt: ${syncEncryptionSaltPrefix(input.salt)}`,
105707
+ `kdf: ${syncEncryptionKdfLabel(input.kdf)}`,
105708
+ `transition: ${input.incompleteTransition ?? SYNC_ENCRYPTION_LOG_ABSENT}`,
105709
+ `lastError: ${lastError ? `${lastError.name} @ ${lastError.at}` : SYNC_ENCRYPTION_LOG_ABSENT}`
105710
+ ];
105711
+ };
105712
+ var init_sync_encryption_diagnostics = __esm(() => {
105713
+ init_log_sanitize();
105714
+ SYNC_ENCRYPTION_LOG_EVENTS = {
105715
+ state: "state",
105716
+ remoteRead: "remote-read",
105717
+ transition: "transition",
105718
+ error: "error",
105719
+ activation: "activation"
105720
+ };
105721
+ HEX_ONLY = /^[0-9a-f]*$/i;
105722
+ URL_IN_MESSAGE = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
105723
+ SYNC_ENCRYPTION_LOG_SENTINELS = [
105724
+ "SYNC_ENCRYPTION_REMOTE_ENCRYPTED",
105725
+ "SYNC_ENCRYPTION_REMOTE_PLAINTEXT",
105726
+ "SYNC_ENCRYPTION_REMOTE_VERSION_UNAVAILABLE",
105727
+ "SYNC_ENCRYPTION_TRANSITION_INCOMPLETE",
105728
+ "SYNC_ENCRYPTION_STATE_UNAVAILABLE",
105729
+ "SYNC_ENCRYPTION_TERMINAL",
105730
+ "SYNC_ENCRYPTION_WRONG_PASSPHRASE",
105731
+ "SYNC_ENCRYPTION_BACKEND_REQUIRED"
105732
+ ];
105733
+ });
105734
+
104707
105735
  // ../../packages/core/src/sync-remote-fence-providers.ts
104708
105736
  var FENCE_MAX_BYTES = 4096, webdavMutationFenceUrl = (documentUrl) => {
104709
- const parsed = new URL(documentUrl);
104710
- const slash = parsed.pathname.lastIndexOf("/");
104711
- parsed.pathname = `${parsed.pathname.slice(0, slash + 1)}${SYNC_REMOTE_MUTATION_FENCE_NAME}`;
104712
- parsed.search = "";
104713
- parsed.hash = "";
104714
- return parsed.toString();
105737
+ const suffixStart = documentUrl.search(/[?#]/);
105738
+ const withoutSuffix = suffixStart === -1 ? documentUrl : documentUrl.slice(0, suffixStart);
105739
+ const pathStart = withoutSuffix.indexOf("/", withoutSuffix.indexOf("://") + 3);
105740
+ const slash = withoutSuffix.lastIndexOf("/");
105741
+ if (pathStart === -1 || slash < pathStart) {
105742
+ return `${withoutSuffix}/${SYNC_REMOTE_MUTATION_FENCE_NAME}`;
105743
+ }
105744
+ return `${withoutSuffix.slice(0, slash + 1)}${SYNC_REMOTE_MUTATION_FENCE_NAME}`;
104715
105745
  }, createWebdavSyncRemoteMutationFencePort = (documentUrl, options = {}) => {
104716
105746
  const url = webdavMutationFenceUrl(documentUrl);
104717
105747
  const readOptions = { ...options, maxBytes: FENCE_MAX_BYTES, treatOversizeAsAbsent: true };
@@ -107261,7 +108291,7 @@ async function undoTaskCompletion(taskId, previousStatus, wasFocusedToday, optio
107261
108291
  return;
107262
108292
  const current = useTaskStore.getState();
107263
108293
  const focusTaskLimit = normalizeFocusTaskLimit(current.settings.gtd?.focusTaskLimit);
107264
- if (current.getDerivedState().focusedCount >= focusTaskLimit)
108294
+ if (current.getFocusedCount() >= focusTaskLimit)
107265
108295
  return;
107266
108296
  const focusResult = await Promise.resolve(current.updateTask(taskId, {
107267
108297
  isFocusedToday: true,
@@ -108541,6 +109571,30 @@ async function cloudGetFile(url, options = {}) {
108541
109571
  return await readResponseBody(res, options.onProgress, options.maxBytes ?? MAX_DOWNLOAD_BYTES, signal);
108542
109572
  });
108543
109573
  }
109574
+ async function cloudAttachmentExists(url, options = {}) {
109575
+ try {
109576
+ return (await cloudHeadJson(url, options)).exists;
109577
+ } catch (error2) {
109578
+ if (isAbortError(error2))
109579
+ throw error2;
109580
+ if (!(error2 instanceof CloudHttpError) || error2.status !== 405)
109581
+ return null;
109582
+ if (!options.partialBodyReads) {
109583
+ options.onHeadUnsupported?.();
109584
+ return null;
109585
+ }
109586
+ }
109587
+ try {
109588
+ await cloudGetFile(url, { ...options, maxBytes: 1, onProgress: undefined });
109589
+ return true;
109590
+ } catch (error2) {
109591
+ if (isAbortError(error2))
109592
+ throw error2;
109593
+ if (error2 instanceof ResponseTooLargeError)
109594
+ return true;
109595
+ return error2 instanceof CloudHttpError && error2.status === 404 ? false : null;
109596
+ }
109597
+ }
108544
109598
  async function cloudDeleteFile(url, options = {}) {
108545
109599
  assertCloudUrl(url, options);
108546
109600
  const fetcher = options.fetcher ?? fetch;
@@ -108623,6 +109677,35 @@ var CLOUDKIT_ATTACHMENT_RECORD_TYPE = "MindwtrAttachment", CLOUDKIT_ATTACHMENT_A
108623
109677
  return recordName || null;
108624
109678
  };
108625
109679
 
109680
+ // ../../packages/core/src/attachment-presence-repair.ts
109681
+ async function repairMissingRemoteAttachments(options) {
109682
+ const { candidates, probe, clear, maxChecks, log } = options;
109683
+ let checked = 0;
109684
+ let cleared = 0;
109685
+ let complete = true;
109686
+ for (const attachment of candidates) {
109687
+ if (maxChecks !== undefined && checked >= maxChecks) {
109688
+ log?.("Attachment presence pass reached the per-pass limit", { limit: String(maxChecks) });
109689
+ break;
109690
+ }
109691
+ checked += 1;
109692
+ const present = await probe(attachment);
109693
+ if (present === null) {
109694
+ complete = false;
109695
+ break;
109696
+ }
109697
+ if (present !== false)
109698
+ continue;
109699
+ clear(attachment);
109700
+ cleared += 1;
109701
+ log?.("Attachment is missing from the sync location; clearing its cloud reference", {
109702
+ id: attachment.id
109703
+ });
109704
+ }
109705
+ return { checked, cleared, complete };
109706
+ }
109707
+ var isAttachmentPresenceRepairCandidate = (attachment) => attachment.kind === "file" && !attachment.deletedAt && Boolean(attachment.cloudKey) && attachment.pendingContentUpload !== true;
109708
+
108626
109709
  // ../../packages/core/src/attachment-draft-settlement.ts
108627
109710
  function planAttachmentDraftSettlement({
108628
109711
  baselineAttachments = [],
@@ -110840,7 +111923,8 @@ CREATE TABLE IF NOT EXISTS projects (
110840
111923
  createdAt TEXT NOT NULL,
110841
111924
  updatedAt TEXT NOT NULL,
110842
111925
  deletedAt TEXT,
110843
- purgedAt TEXT
111926
+ purgedAt TEXT,
111927
+ startDate TEXT
110844
111928
  );
110845
111929
 
110846
111930
  CREATE TABLE IF NOT EXISTS areas (
@@ -111247,7 +112331,7 @@ var toJson = (value) => value === undefined ? null : JSON.stringify(value), from
111247
112331
  });
111248
112332
  return fallback;
111249
112333
  }
111250
- }, toBool = (value) => value ? 1 : 0, fromBool = (value) => Boolean(value), toNullableBool = (value) => value === null || value === undefined ? null : toBool(value), fromNullableBool = (value) => {
112334
+ }, toBool = (value) => value ? 1 : 0, fromBool = (value) => Boolean(value), fromPresentBool = (value) => value ? true : undefined, toNullableBool = (value) => value === null || value === undefined ? null : toBool(value), fromNullableBool = (value) => {
111251
112335
  if (value === null)
111252
112336
  return null;
111253
112337
  if (value === undefined)
@@ -111377,7 +112461,7 @@ var schema, TASK_SYNC_SCHEMA_VERSION, TASK_SYNC_SCHEMA_VERSION_POLICY, TASK_SYNC
111377
112461
  relativeStartOffset: fromJson(row.relativeStartOffset, undefined),
111378
112462
  dueDate: fromOptional(row.dueDate),
111379
112463
  recurrence: fromJson(row.recurrence, null),
111380
- showFutureRecurrence: fromBool(row.showFutureRecurrence),
112464
+ showFutureRecurrence: fromPresentBool(row.showFutureRecurrence),
111381
112465
  pushCount: row.pushCount === null || row.pushCount === undefined ? undefined : Number(row.pushCount),
111382
112466
  repeatReminderMinutes: row.repeatReminderMinutes === null || row.repeatReminderMinutes === undefined ? undefined : Number(row.repeatReminderMinutes),
111383
112467
  tags: toStringArray(fromJson(row.tags, [])),
@@ -111453,7 +112537,8 @@ var init_project_sync_schema_fixture = __esm(() => {
111453
112537
  { name: "createdAt", nullability: "required", cloudSynced: true, cloudWrite: "managed", sqliteColumn: "createdAt", sqliteOrder: 18, sqliteType: "TEXT" },
111454
112538
  { name: "updatedAt", nullability: "required", cloudSynced: true, cloudWrite: "managed", sqliteColumn: "updatedAt", sqliteOrder: 19, sqliteType: "TEXT" },
111455
112539
  { name: "deletedAt", nullability: "optional", cloudSynced: true, cloudWrite: "patch", sqliteColumn: "deletedAt", sqliteOrder: 20, sqliteType: "TEXT" },
111456
- { name: "purgedAt", nullability: "optional", cloudSynced: true, cloudWrite: "patch", sqliteColumn: "purgedAt", sqliteOrder: 21, sqliteType: "TEXT" }
112540
+ { name: "purgedAt", nullability: "optional", cloudSynced: true, cloudWrite: "patch", sqliteColumn: "purgedAt", sqliteOrder: 21, sqliteType: "TEXT" },
112541
+ { name: "startDate", nullability: "optional", cloudSynced: true, cloudWrite: "create-patch", sqliteColumn: "startDate", sqliteOrder: 22, sqliteType: "TEXT" }
111457
112542
  ],
111458
112543
  fixture: {
111459
112544
  id: "project-schema-fixture",
@@ -111484,7 +112569,8 @@ var init_project_sync_schema_fixture = __esm(() => {
111484
112569
  createdAt: "2026-07-01T10:00:00.000Z",
111485
112570
  updatedAt: "2026-07-14T11:00:00.000Z",
111486
112571
  deletedAt: "2026-07-14T11:30:00.000Z",
111487
- purgedAt: "2026-07-14T11:45:00.000Z"
112572
+ purgedAt: "2026-07-14T11:45:00.000Z",
112573
+ startDate: "2026-07-10T09:00:00.000Z"
111488
112574
  }
111489
112575
  };
111490
112576
  });
@@ -111515,6 +112601,7 @@ var schema2, PROJECT_SYNC_SCHEMA_VERSION, PROJECT_SYNC_FIELD_SCHEMA, PROJECT_SYN
111515
112601
  supportNotes: project.supportNotes ?? null,
111516
112602
  attachments: toJson(project.attachments),
111517
112603
  dueDate: project.dueDate ?? null,
112604
+ startDate: project.startDate ?? null,
111518
112605
  reviewAt: project.reviewAt ?? null,
111519
112606
  areaId: project.areaId ?? null,
111520
112607
  areaTitle: project.areaTitle ?? null,
@@ -111541,6 +112628,7 @@ var schema2, PROJECT_SYNC_SCHEMA_VERSION, PROJECT_SYNC_FIELD_SCHEMA, PROJECT_SYN
111541
112628
  supportNotes: fromOptional(row.supportNotes),
111542
112629
  attachments: toAttachments(fromJson(row.attachments, undefined)),
111543
112630
  dueDate: fromOptional(row.dueDate),
112631
+ startDate: fromOptional(row.startDate),
111544
112632
  reviewAt: fromOptional(row.reviewAt),
111545
112633
  areaId: fromOptional(row.areaId),
111546
112634
  areaTitle: fromOptional(row.areaTitle),
@@ -115681,6 +116769,7 @@ function applyImport(currentData, parsed, opts) {
115681
116769
  tagIds: project.tagIds ?? [],
115682
116770
  supportNotes: project.supportNotes,
115683
116771
  dueDate: project.dueDate,
116772
+ startDate: project.startDate,
115684
116773
  createdAt,
115685
116774
  updatedAt,
115686
116775
  rev: nextRevision(),
@@ -116691,6 +117780,7 @@ var normalizeFolders = (rawFolders) => {
116691
117780
  areaSourceId: record3.folderId,
116692
117781
  color: record3.color,
116693
117782
  dueDate: record3.dueDate,
117783
+ startDate: record3.startDate,
116694
117784
  supportNotes,
116695
117785
  isArchived: Boolean(record3.completedAt),
116696
117786
  createdAt: record3.createdAt,
@@ -117068,6 +118158,7 @@ ${next}`;
117068
118158
  const project2 = ensureProjectRecord(projectsByKey, row.name, allocateProjectOrder);
117069
118159
  project2.status = parseProjectStatus(row.statusText || "");
117070
118160
  project2.dueDate = dueMapping.value ?? project2.dueDate;
118161
+ project2.startDate = startMapping.value ?? project2.startDate;
117071
118162
  project2.supportNotes = mergeProjectSupportNotes(project2.supportNotes, joinDescription([
117072
118163
  row.notes,
117073
118164
  plannedMapping.value ? `Planned date in OmniFocus: ${plannedMapping.value}` : undefined,
@@ -117459,6 +118550,7 @@ ${next}`;
117459
118550
  areaSourceKey: ensureAreaRecord(project.folderId, project.folderName),
117460
118551
  status: project.completed || rootTask?.completed ? "archived" : parseProjectStatus(project.statusText || rootTask?.statusText || ""),
117461
118552
  dueDate: dateNotes.dueDate,
118553
+ startDate: dateNotes.startTime,
117462
118554
  supportNotes: joinDescription([
117463
118555
  project.note,
117464
118556
  rootTask?.note,
@@ -117489,6 +118581,7 @@ ${next}`;
117489
118581
  order: projects.length,
117490
118582
  status: task.completed ? "archived" : parseProjectStatus(task.statusText || ""),
117491
118583
  dueDate: dateNotes.dueDate,
118584
+ startDate: dateNotes.startTime,
117492
118585
  supportNotes: joinDescription([
117493
118586
  task.note,
117494
118587
  dateNotes.plannedNote,
@@ -120338,6 +121431,7 @@ var init_settings_search_keys = __esm(() => {
120338
121431
  SETTINGS_SEARCH_PAGE_IDS = Object.keys(SETTINGS_SEARCH_PAGE_KEYS);
120339
121432
  SETTINGS_SEARCH_INDEX = SETTINGS_SEARCH_PAGE_IDS.flatMap((pageId) => getSettingsSearchEntries(pageId));
120340
121433
  SETTINGS_SEARCH_MOBILE_EXCLUSIONS = {
121434
+ featureTimeline: "Timeline view exists on desktop only; the mobile GTD > Features screen has no row for it (#1145).",
120341
121435
  density: "No adjustable list density setting on mobile.",
120342
121436
  textSize: "Mobile follows the OS text-size setting automatically; no in-app override.",
120343
121437
  keybindings: "No hardware-keyboard shortcuts configuration on mobile.",
@@ -120474,6 +121568,11 @@ __export(exports_src, {
120474
121568
  taskDraftToChangedUpdatePatch: () => taskDraftToChangedUpdatePatch,
120475
121569
  tFallback: () => tFallback,
120476
121570
  syncPlaintextArtifactName: () => syncPlaintextArtifactName,
121571
+ syncEncryptionScopeLabel: () => syncEncryptionScopeLabel,
121572
+ syncEncryptionSaltPrefix: () => syncEncryptionSaltPrefix,
121573
+ syncEncryptionLogMessage: () => syncEncryptionLogMessage,
121574
+ syncEncryptionKdfLabel: () => syncEncryptionKdfLabel,
121575
+ syncEncryptionArtifactLabel: () => syncEncryptionArtifactLabel,
120477
121576
  syncEncryptedArtifactName: () => syncEncryptedArtifactName,
120478
121577
  summarizeTaskLifecycleCounts: () => summarizeTaskLifecycleCounts,
120479
121578
  summarizeMergeStats: () => summarizeMergeStats,
@@ -120513,6 +121612,7 @@ __export(exports_src, {
120513
121612
  setTaskViewSectionId: () => setTaskViewSectionId,
120514
121613
  setTaskDraftField: () => setTaskDraftField,
120515
121614
  setStorageAdapter: () => setStorageAdapter,
121615
+ setSleepBypass: () => setSleepBypass,
120516
121616
  setSha256HexProvider: () => setSha256HexProvider,
120517
121617
  setLogger: () => setLogger,
120518
121618
  setComposerTitle: () => setComposerTitle,
@@ -120539,6 +121639,7 @@ __export(exports_src, {
120539
121639
  selectVisiblePeople: () => selectVisiblePeople,
120540
121640
  selectVisibleAreas: () => selectVisibleAreas,
120541
121641
  selectProcessInboxCandidates: () => selectProcessInboxCandidates,
121642
+ selectFocusedCount: () => selectFocusedCount,
120542
121643
  selectComposerTask: () => selectComposerTask,
120543
121644
  sectionToSqliteRow: () => sectionToSqliteRow,
120544
121645
  sectionFromSqliteRow: () => sectionFromSqliteRow,
@@ -120627,6 +121728,7 @@ __export(exports_src, {
120627
121728
  resolveAnthropicModel: () => resolveAnthropicModel,
120628
121729
  resetUnhashableAttachmentStatsForTests: () => resetUnhashableAttachmentStatsForTests,
120629
121730
  resetPomodoroState: () => resetPomodoroState,
121731
+ resetLastSyncEncryptionError: () => resetLastSyncEncryptionError,
120630
121732
  resetHeartbeatOptOutMarker: () => resetHeartbeatOptOutMarker,
120631
121733
  resetForTests: () => resetForTests,
120632
121734
  rescheduleTask: () => rescheduleTask,
@@ -120634,6 +121736,7 @@ __export(exports_src, {
120634
121736
  replaceEntityInMap: () => replaceEntityInMap,
120635
121737
  replaceEntityInArray: () => replaceEntityInArray,
120636
121738
  replaceEntitiesInArray: () => replaceEntitiesInArray,
121739
+ repairMissingRemoteAttachments: () => repairMissingRemoteAttachments,
120637
121740
  repairMergedSyncReferences: () => repairMergedSyncReferences,
120638
121741
  removeAdvancedFilterCriteriaChip: () => removeAdvancedFilterCriteriaChip,
120639
121742
  recordUpdateReminderShown: () => recordUpdateReminderShown,
@@ -120829,6 +121932,7 @@ __export(exports_src, {
120829
121932
  isSyncFilePath: () => isSyncFilePath,
120830
121933
  isSyncFileLockUnavailableError: () => isSyncFileLockUnavailableError,
120831
121934
  isSyncFileGenerationCorruptError: () => isSyncFileGenerationCorruptError,
121935
+ isSyncEncryptionStateBlocked: () => isSyncEncryptionStateBlocked,
120832
121936
  isSyncEncryptionRemoteVersionUnavailableError: () => isSyncEncryptionRemoteVersionUnavailableError,
120833
121937
  isSupportedLanguage: () => isSupportedLanguage,
120834
121938
  isSlotFreeForDay: () => isSlotFreeForDay,
@@ -120858,6 +121962,7 @@ __export(exports_src, {
120858
121962
  isMindwtrMirrorEvent: () => isMindwtrMirrorEvent,
120859
121963
  isMindwtrMirrorCalendar: () => isMindwtrMirrorCalendar,
120860
121964
  isMarkdownEditorAssistEnabled: () => isMarkdownEditorAssistEnabled,
121965
+ isLocalPersistEquivalent: () => isLocalPersistEquivalent,
120861
121966
  isLikelyOfflineSyncError: () => isLikelyOfflineSyncError,
120862
121967
  isJalaliCalendarLocale: () => isJalaliCalendarLocale,
120863
121968
  isFocusSequentialCandidate: () => isFocusSequentialCandidate,
@@ -120869,12 +121974,14 @@ __export(exports_src, {
120869
121974
  isDropboxPathNotFoundTag: () => isDropboxPathNotFoundTag,
120870
121975
  isDropboxPathConflictTag: () => isDropboxPathConflictTag,
120871
121976
  isDropboxConflictError: () => isDropboxConflictError,
121977
+ isDeepJsonEqual: () => isDeepJsonEqual,
120872
121978
  isCustomTimeEstimate: () => isCustomTimeEstimate,
120873
121979
  isConnectionAllowed: () => isConnectionAllowed,
120874
121980
  isCompletedCalendarTask: () => isCompletedCalendarTask,
120875
121981
  isCalendarFeedTask: () => isCalendarFeedTask,
120876
121982
  isAttachmentUploadTooLargeError: () => isAttachmentUploadTooLargeError,
120877
121983
  isAttachmentUploadAdmissionError: () => isAttachmentUploadAdmissionError,
121984
+ isAttachmentPresenceRepairCandidate: () => isAttachmentPresenceRepairCandidate,
120878
121985
  isAttachmentLocalResourceReferenced: () => isAttachmentLocalResourceReferenced,
120879
121986
  isAttachmentCloudResourceReferenced: () => isAttachmentCloudResourceReferenced,
120880
121987
  isAreaFilterSelectionActive: () => isAreaFilterSelectionActive,
@@ -120986,6 +122093,7 @@ __export(exports_src, {
120986
122093
  getLocalizedWeekdayLabel: () => getLocalizedWeekdayLabel,
120987
122094
  getLocalizedWeekdayButtons: () => getLocalizedWeekdayButtons,
120988
122095
  getLocaleCoverageTier: () => getLocaleCoverageTier,
122096
+ getLastSyncEncryptionError: () => getLastSyncEncryptionError,
120989
122097
  getInlineMarkdownPreview: () => getInlineMarkdownPreview,
120990
122098
  getInMemorySyncChangeFingerprint: () => getInMemorySyncChangeFingerprint,
120991
122099
  getInMemoryAppDataSnapshot: () => getInMemoryAppDataSnapshot,
@@ -121039,6 +122147,7 @@ __export(exports_src, {
121039
122147
  formatTaskMovedMessage: () => formatTaskMovedMessage,
121040
122148
  formatTaskMarkedDoneMessage: () => formatTaskMarkedDoneMessage,
121041
122149
  formatSyncErrorMessage: () => formatSyncErrorMessage,
122150
+ formatSyncEncryptionDiagnostics: () => formatSyncEncryptionDiagnostics,
121042
122151
  formatSettingsSearchPath: () => formatSettingsSearchPath,
121043
122152
  formatRecurrenceLabel: () => formatRecurrenceLabel,
121044
122153
  formatRecurrenceCountLabel: () => formatRecurrenceCountLabel,
@@ -121055,6 +122164,7 @@ __export(exports_src, {
121055
122164
  formatCalendarDurationLabel: () => formatCalendarDurationLabel,
121056
122165
  formatAIErrorAlertBody: () => formatAIErrorAlertBody,
121057
122166
  flushPendingSave: () => flushPendingSave,
122167
+ findSyncEncryptionSentinel: () => findSyncEncryptionSentinel,
121058
122168
  findSelectableProjectByTitleAndArea: () => findSelectableProjectByTitleAndArea,
121059
122169
  findPendingAttachmentUploads: () => findPendingAttachmentUploads,
121060
122170
  findOrphanedAttachments: () => findOrphanedAttachments,
@@ -121132,6 +122242,7 @@ __export(exports_src, {
121132
122242
  createImportDiagnostic: () => createImportDiagnostic,
121133
122243
  createImportArchiveBudget: () => createImportArchiveBudget,
121134
122244
  createDropboxSyncRemoteMutationFencePort: () => createDropboxSyncRemoteMutationFencePort,
122245
+ createDropboxAttachmentPresenceIndex: () => createDropboxAttachmentPresenceIndex,
121135
122246
  createDefaultSyncRunStoreBridge: () => createDefaultSyncRunStoreBridge,
121136
122247
  createCustomTimeEstimate: () => createCustomTimeEstimate,
121137
122248
  createCurrentRecurringCalendarTask: () => createCurrentRecurringCalendarTask,
@@ -121185,9 +122296,11 @@ __export(exports_src, {
121185
122296
  cloudGetJson: () => cloudGetJson,
121186
122297
  cloudGetFile: () => cloudGetFile,
121187
122298
  cloudDeleteFile: () => cloudDeleteFile,
122299
+ cloudAttachmentExists: () => cloudAttachmentExists,
121188
122300
  cloneSettings: () => cloneSettings,
121189
122301
  cloneAppData: () => cloneAppData,
121190
122302
  clearProviderModelsCache: () => clearProviderModelsCache,
122303
+ clearIdleSyncCycleSnapshot: () => clearIdleSyncCycleSnapshot,
121191
122304
  clearDeletedTaskProjectArchiveMetadata: () => clearDeletedTaskProjectArchiveMetadata,
121192
122305
  clearBreadcrumbs: () => clearBreadcrumbs,
121193
122306
  checkAttachmentContentChange: () => checkAttachmentContentChange,
@@ -121205,6 +122318,12 @@ __export(exports_src, {
121205
122318
  buildSyncPayloadTraceExtra: () => buildSyncPayloadTraceExtra,
121206
122319
  buildSyncPayloadSurfaceTraceExtra: () => buildSyncPayloadSurfaceTraceExtra,
121207
122320
  buildSyncPayloadDiffTraceExtra: () => buildSyncPayloadDiffTraceExtra,
122321
+ buildSyncLocationScope: () => buildSyncLocationScope,
122322
+ buildSyncEncryptionTransitionExtra: () => buildSyncEncryptionTransitionExtra,
122323
+ buildSyncEncryptionStateExtra: () => buildSyncEncryptionStateExtra,
122324
+ buildSyncEncryptionRemoteReadExtra: () => buildSyncEncryptionRemoteReadExtra,
122325
+ buildSyncEncryptionErrorExtra: () => buildSyncEncryptionErrorExtra,
122326
+ buildSyncEncryptionActivationExtra: () => buildSyncEncryptionActivationExtra,
121208
122327
  buildSpeechTranscriptionPrompt: () => buildSpeechTranscriptionPrompt,
121209
122328
  buildSpeechToTaskPrompt: () => buildSpeechToTaskPrompt,
121210
122329
  buildSettingsSearchResults: () => buildSettingsSearchResults,
@@ -121359,8 +122478,13 @@ __export(exports_src, {
121359
122478
  SYNC_FILE_GENERATION_CORRUPT_CODE: () => SYNC_FILE_GENERATION_CORRUPT_CODE,
121360
122479
  SYNC_ENCRYPTION_TRANSITION_INCOMPLETE: () => SYNC_ENCRYPTION_TRANSITION_INCOMPLETE,
121361
122480
  SYNC_ENCRYPTION_REMOTE_VERSION_UNAVAILABLE: () => SYNC_ENCRYPTION_REMOTE_VERSION_UNAVAILABLE,
122481
+ SYNC_ENCRYPTION_LOG_SENTINELS: () => SYNC_ENCRYPTION_LOG_SENTINELS,
122482
+ SYNC_ENCRYPTION_LOG_PREFIX: () => SYNC_ENCRYPTION_LOG_PREFIX,
122483
+ SYNC_ENCRYPTION_LOG_EVENTS: () => SYNC_ENCRYPTION_LOG_EVENTS,
122484
+ SYNC_ENCRYPTION_LOG_ABSENT: () => SYNC_ENCRYPTION_LOG_ABSENT,
121362
122485
  SYNC_ENCRYPTION_KEYED_STATES: () => SYNC_ENCRYPTION_KEYED_STATES,
121363
122486
  SYNC_CRYPTO_DEFAULT_KDF_PARAMS: () => SYNC_CRYPTO_DEFAULT_KDF_PARAMS,
122487
+ SUSPENDED_REQUEST_MESSAGE: () => SUSPENDED_REQUEST_MESSAGE,
121364
122488
  SUPPORTED_LANGUAGES: () => SUPPORTED_LANGUAGES,
121365
122489
  STORE_REVIEW_MIN_DAYS_SINCE_FIRST_SEEN: () => STORE_REVIEW_MIN_DAYS_SINCE_FIRST_SEEN,
121366
122490
  STORE_REVIEW_MIN_ACTIVE_DAYS: () => STORE_REVIEW_MIN_ACTIVE_DAYS,
@@ -121463,6 +122587,7 @@ __export(exports_src, {
121463
122587
  DropboxFileNotFoundError: () => DropboxFileNotFoundError,
121464
122588
  DropboxConflictError: () => DropboxConflictError,
121465
122589
  DataTransferRefreshError: () => DataTransferRefreshError,
122590
+ DROPBOX_ATTACHMENTS_PATH: () => DROPBOX_ATTACHMENTS_PATH,
121466
122591
  DRACULA_EXTERNAL_CALENDAR_COLOR_MAP: () => DRACULA_EXTERNAL_CALENDAR_COLOR_MAP,
121467
122592
  DRACULA_CONTEXT_COLOR_PALETTE: () => DRACULA_CONTEXT_COLOR_PALETTE,
121468
122593
  DONATION_PROMPT_SUPPORT_CLICK_COOLDOWN_MS: () => DONATION_PROMPT_SUPPORT_CLICK_COOLDOWN_MS,
@@ -121569,6 +122694,7 @@ var init_src = __esm(() => {
121569
122694
  init_sync_fast_sync();
121570
122695
  init_sync_crypto();
121571
122696
  init_sync_encryption();
122697
+ init_sync_encryption_diagnostics();
121572
122698
  init_sync_remote_fence();
121573
122699
  init_sync_remote_fence_providers();
121574
122700
  init_diceware();
@@ -135064,6 +136190,7 @@ var createCloudService = (options) => {
135064
136190
  isSequential: input.isSequential,
135065
136191
  isFocused: input.isFocused,
135066
136192
  dueDate: input.dueDate ?? undefined,
136193
+ startDate: input.startDate ?? undefined,
135067
136194
  reviewAt: input.reviewAt ?? undefined,
135068
136195
  supportNotes: input.supportNotes ?? undefined
135069
136196
  })
@@ -135086,6 +136213,8 @@ var createCloudService = (options) => {
135086
136213
  patch.isFocused = input.isFocused;
135087
136214
  if (input.dueDate !== undefined)
135088
136215
  patch.dueDate = input.dueDate;
136216
+ if (input.startDate !== undefined)
136217
+ patch.startDate = input.startDate;
135089
136218
  if (input.reviewAt !== undefined)
135090
136219
  patch.reviewAt = input.reviewAt;
135091
136220
  if (input.supportNotes !== undefined)
@@ -137593,7 +138722,8 @@ var getProjectColumns = (db) => {
137593
138722
  const names = new Set(columns.map((col) => String(col.name)));
137594
138723
  const hasOrderNum = names.has("orderNum");
137595
138724
  const hasDueDate = names.has("dueDate");
137596
- const selectColumns = BASE_PROJECT_COLUMNS.filter((name) => names.has(name) && (hasOrderNum || name !== "orderNum") && (hasDueDate || name !== "dueDate"));
138725
+ const hasStartDate = names.has("startDate");
138726
+ const selectColumns = BASE_PROJECT_COLUMNS.filter((name) => names.has(name) && (hasOrderNum || name !== "orderNum") && (hasDueDate || name !== "dueDate") && (hasStartDate || name !== "startDate"));
137597
138727
  const resolved = { hasOrderNum, selectColumns };
137598
138728
  projectColumnsCache.set(db, resolved);
137599
138729
  return resolved;
@@ -138508,6 +139638,7 @@ var createService = (options, deps = defaultServiceDeps) => {
138508
139638
  isSequential: input.isSequential,
138509
139639
  isFocused: input.isFocused,
138510
139640
  dueDate: input.dueDate ?? undefined,
139641
+ startDate: input.startDate ?? undefined,
138511
139642
  reviewAt: input.reviewAt ?? undefined,
138512
139643
  supportNotes: input.supportNotes ?? undefined
138513
139644
  })
@@ -138529,6 +139660,8 @@ var createService = (options, deps = defaultServiceDeps) => {
138529
139660
  updates.isFocused = input.isFocused;
138530
139661
  if (input.dueDate !== undefined)
138531
139662
  updates.dueDate = input.dueDate ?? undefined;
139663
+ if (input.startDate !== undefined)
139664
+ updates.startDate = input.startDate ?? undefined;
138532
139665
  if (input.reviewAt !== undefined)
138533
139666
  updates.reviewAt = input.reviewAt ?? undefined;
138534
139667
  if (input.supportNotes !== undefined)
@@ -138905,6 +140038,7 @@ var addProjectSchema = objectType({
138905
140038
  isSequential: booleanType().optional(),
138906
140039
  isFocused: booleanType().optional(),
138907
140040
  dueDate: isoDateLikeSchema.nullable().optional(),
140041
+ startDate: isoDateLikeSchema.nullable().optional(),
138908
140042
  reviewAt: isoDateLikeSchema.nullable().optional(),
138909
140043
  supportNotes: stringType().nullable().optional()
138910
140044
  });
@@ -138917,6 +140051,7 @@ var updateProjectSchema = objectType({
138917
140051
  isSequential: booleanType().optional(),
138918
140052
  isFocused: booleanType().optional(),
138919
140053
  dueDate: isoDateLikeSchema.nullable().optional(),
140054
+ startDate: isoDateLikeSchema.nullable().optional(),
138920
140055
  reviewAt: isoDateLikeSchema.nullable().optional(),
138921
140056
  supportNotes: stringType().nullable().optional()
138922
140057
  });