mindwtr-mcp 1.1.5 → 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 +2061 -331
  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;
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;
3113
3129
  }
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;
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;
3299
3350
  } else {
3300
- output += escape(input[i]);
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;
3477
+ } else {
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,
@@ -12171,6 +12472,7 @@ var init_vi = __esm(() => {
12171
12472
  "bulk.keepStatus": "Giữ trạng thái",
12172
12473
  "bulk.keepProject": "Giữ dự án",
12173
12474
  "bulk.keepArea": "Giữ khu vực",
12475
+ "bulk.keepSection": "Giữ phần",
12174
12476
  "bulk.waitingPersonRequired": "Chọn người mà các mục này đang chờ.",
12175
12477
  "bulk.deleting": "Đang xóa các nhiệm vụ đã chọn...",
12176
12478
  "taskEdit.aiAssistant": "Trợ lý AI",
@@ -12189,6 +12491,9 @@ var init_vi = __esm(() => {
12189
12491
  "nav.main": "Chính",
12190
12492
  "nav.inbox": "Hộp thư đến",
12191
12493
  "nav.board": "Bảng Kanban",
12494
+ "nav.timeline": "Dòng thời gian",
12495
+ "timeline.empty": "Chưa có gì được lên lịch",
12496
+ "timeline.emptyHint": "Các công việc có ngày bắt đầu hoặc ngày đến hạn sẽ hiển thị ở đây dưới dạng thanh.",
12192
12497
  "nav.projects": "Dự án",
12193
12498
  "nav.contexts": "Ngữ cảnh",
12194
12499
  "nav.next": "Hành động tiếp theo",
@@ -12267,6 +12572,7 @@ var init_vi = __esm(() => {
12267
12572
  "keybindings.goReference": "Đến Tham khảo",
12268
12573
  "keybindings.goCalendar": "Đến Lịch",
12269
12574
  "keybindings.goBoard": "Đến Bảng Kanban",
12575
+ "keybindings.goTimeline": "Đến Dòng thời gian",
12270
12576
  "keybindings.goDone": "Đến Hoàn thành",
12271
12577
  "keybindings.goArchived": "Đến Lưu trữ",
12272
12578
  "keybindings.switchArea": "Chuyển sang Khu vực 1-9",
@@ -12427,6 +12733,7 @@ var init_vi = __esm(() => {
12427
12733
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
12428
12734
  "attachments.obsidianLinkInputHint": 'Dán liên kết ghi chú obsidian:// hoặc dùng "Tiêu đề | obsidian://...".',
12429
12735
  "attachments.fileNotSupported": "Tệp đính kèm chỉ được hỗ trợ trên ứng dụng máy tính.",
12736
+ "attachments.webUnavailable": "Tệp đính kèm này không khả dụng trong ứng dụng web.",
12430
12737
  "attachments.fileTooLarge": "Tệp quá lớn để tải lên.",
12431
12738
  "attachments.fileNotReadable": "Không thể đọc tệp này nên tệp chưa được đính kèm. Hãy chuyển tệp sang thư mục khác và thử lại.",
12432
12739
  "attachments.linkToFile": "Liên kết tới tệp…",
@@ -13348,6 +13655,10 @@ var init_vi = __esm(() => {
13348
13655
  "settings.featureTimeEstimatesDesc": "Thêm ước tính thời lượng nhanh để chặn thời gian.",
13349
13656
  "settings.featurePomodoro": "Hẹn giờ Pomodoro",
13350
13657
  "settings.featurePomodoroDesc": "Bật bảng Pomodoro tùy chọn trong chế độ Tập trung.",
13658
+ "settings.featureTimeline": "Chế độ xem Dòng thời gian",
13659
+ "settings.featureTimelineDesc": "Hiển thị dòng thời gian chỉ đọc của các công việc có ngày trong thanh bên.",
13660
+ "settings.sidebarViews": "Chế độ xem trên thanh bên",
13661
+ "settings.sidebarViewsDesc": "Chọn chế độ xem nào hiển thị trên thanh bên. Chế độ xem bị ẩn vẫn mở được qua tìm kiếm.",
13351
13662
  "settings.pomodoroCustomPreset": "Cài đặt sẵn tùy chỉnh",
13352
13663
  "settings.pomodoroCustomPresetDesc": "Thêm một cài đặt sẵn tập trung/nghỉ thêm. Khớp với cài đặt sẵn tích hợp chỉ giữ các chip tích hợp.",
13353
13664
  "settings.pomodoroFocusMinutes": "Phút tập trung",
@@ -14463,6 +14774,7 @@ var init_zh_Hans = __esm(() => {
14463
14774
  "bulk.keepStatus": "保留状态",
14464
14775
  "bulk.keepProject": "保留项目",
14465
14776
  "bulk.keepArea": "保留领域",
14777
+ "bulk.keepSection": "保留分区",
14466
14778
  "bulk.waitingPersonRequired": "选择这些事项在等待谁。",
14467
14779
  "bulk.deleting": "正在删除所选任务…",
14468
14780
  "taskEdit.aiAssistant": "AI 助手",
@@ -14480,6 +14792,9 @@ var init_zh_Hans = __esm(() => {
14480
14792
  "nav.main": "主页",
14481
14793
  "nav.inbox": "收集箱",
14482
14794
  "nav.board": "看板",
14795
+ "nav.timeline": "时间线",
14796
+ "timeline.empty": "还没有排期的任务",
14797
+ "timeline.emptyHint": "设置了开始日期或截止日期的任务会在这里显示为色条。",
14483
14798
  "nav.projects": "项目",
14484
14799
  "nav.contexts": "情境",
14485
14800
  "nav.next": "下一步行动",
@@ -14557,6 +14872,7 @@ var init_zh_Hans = __esm(() => {
14557
14872
  "keybindings.goReference": "前往参考",
14558
14873
  "keybindings.goCalendar": "前往日历",
14559
14874
  "keybindings.goBoard": "前往看板",
14875
+ "keybindings.goTimeline": "前往时间线",
14560
14876
  "keybindings.goDone": "前往已完成",
14561
14877
  "keybindings.goArchived": "前往归档",
14562
14878
  "keybindings.list.nextPrev": "上下移动选中项",
@@ -14711,9 +15027,12 @@ var init_zh_Hans = __esm(() => {
14711
15027
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
14712
15028
  "attachments.obsidianLinkInputHint": "粘贴 obsidian:// 笔记链接,或使用“标题 | obsidian://...”。",
14713
15029
  "attachments.fileNotSupported": "文件附件仅在桌面应用中支持。",
15030
+ "attachments.webUnavailable": "此附件在网页版中无法使用。",
14714
15031
  "attachments.fileTooLarge": "文件过大,无法上传。",
14715
15032
  "attachments.fileNotReadable": "无法读取此文件,因此未添加附件。请将文件移动到其他文件夹后重试。",
14716
15033
  "attachments.linkToFile": "链接到文件…",
15034
+ "attachments.linkedFileElsewhere": "此链接指向另一台设备上的文件:{{path}}。请在那台设备上打开,或改为直接添加该文件作为附件。",
15035
+ "attachments.openLinkFailed": "无法打开此链接。",
14717
15036
  "attachments.invalidFileType": "不支持的文件类型。",
14718
15037
  "attachments.invalidLink": "请输入有效的链接。",
14719
15038
  "attachments.photoUnavailableTitle": "图片选择不可用",
@@ -15055,7 +15374,7 @@ var init_zh_Hans = __esm(() => {
15055
15374
  "projects.sectionPlaceholder": "分区标题",
15056
15375
  "projects.noSection": "无分区",
15057
15376
  "projects.sectionEmpty": "暂无任务",
15058
- "projects.deleteSectionConfirm": "确定要删除此分区吗?",
15377
+ "projects.deleteSectionConfirm": "删除此分区?其中的任务不会被删除,会移到“无分区”。",
15059
15378
  "projects.areaFilter": "领域筛选",
15060
15379
  "projects.allAreas": "所有领域",
15061
15380
  "projects.noArea": "无领域",
@@ -15082,6 +15401,8 @@ var init_zh_Hans = __esm(() => {
15082
15401
  "projects.complete": "完成",
15083
15402
  "projects.archive": "归档",
15084
15403
  "projects.reactivate": "重新激活",
15404
+ "projects.archivedTaskInspectionHint": "双击查看此任务。重新激活项目后才能编辑。",
15405
+ "projects.archivedReadOnlyHint": "项目已归档。重新激活后才能编辑此任务。",
15085
15406
  "projects.actionsLabel": "操作",
15086
15407
  "projects.archiveHelp": "归档项目会将其和剩余任务标记为完成,可随时重新激活。",
15087
15408
  "projects.completeConfirm": "将此项目标记为完成并结束其所有任务?",
@@ -15364,6 +15685,7 @@ var init_zh_Hans = __esm(() => {
15364
15685
  "settings.material3ThemeDesc": "在 Android 上使用 Material 3 配色",
15365
15686
  "settings.selectLang": "选择您的首选语言",
15366
15687
  "settings.languagePartlyTranslated": "部分翻译",
15688
+ "settings.videoTutorials": "视频教程",
15367
15689
  "settings.privacy": "隐私",
15368
15690
  "settings.mobile.appLock": "应用锁",
15369
15691
  "settings.mobile.appLockDesc": "打开 Mindwtr 或返回应用时需要设备锁验证。它保护应用界面,不加密设备上的数据库。",
@@ -15451,6 +15773,8 @@ var init_zh_Hans = __esm(() => {
15451
15773
  "settings.syncEncryptionUnlock": "输入密码短语",
15452
15774
  "settings.syncEncryptionDecline": "暂不",
15453
15775
  "settings.syncEncryptionPausedDesc": "在您输入密码短语之前,本设备的自动同步将保持暂停。",
15776
+ "settings.syncEncryptionLockedRecheckHint": "如果该同步位置已不再存放加密文件,请点按“立即同步”,本设备会重新检查该位置并继续同步。",
15777
+ "settings.syncEncryptionNoEncryptedRemote": "该同步位置已没有加密文件,因此本设备的加密现已关闭。如需加密该位置,请重新开启。",
15454
15778
  "settings.syncEncryptionRemoteEncrypted": "此同步位置已加密。请输入其同步密码短语以继续同步。",
15455
15779
  "settings.syncEncryptionRemotePlaintext": "同步已停止:此同步位置不再加密。请在此设备上关闭同步加密,或在该同步位置重新启用加密。",
15456
15780
  "settings.syncEncryptionRemotePlaintextDesc": "另一台设备在此同步位置关闭了加密。此设备上的内容没有被更改或降级。若要以明文继续同步,请在此设备上关闭同步加密;否则请在该同步位置重新启用加密。",
@@ -15680,6 +16004,10 @@ var init_zh_Hans = __esm(() => {
15680
16004
  "settings.featureTimeEstimatesDesc": "为时间管理添加时长估计。",
15681
16005
  "settings.featurePomodoro": "番茄钟",
15682
16006
  "settings.featurePomodoroDesc": "在聚焦视图中启用可选的番茄钟面板。",
16007
+ "settings.featureTimeline": "时间线视图",
16008
+ "settings.featureTimelineDesc": "在侧边栏显示有日期任务的只读时间线。",
16009
+ "settings.sidebarViews": "侧边栏视图",
16010
+ "settings.sidebarViewsDesc": "选择侧边栏中显示哪些视图。隐藏的视图仍可通过搜索打开。",
15683
16011
  "settings.pomodoroCustomPreset": "自定义预设",
15684
16012
  "settings.pomodoroCustomPresetDesc": "添加一个额外的专注/休息预设。若与内置预设相同,将继续只显示内置选项。",
15685
16013
  "settings.pomodoroFocusMinutes": "专注分钟",
@@ -15743,6 +16071,7 @@ var init_zh_Hans = __esm(() => {
15743
16071
  "tags.title": "标签",
15744
16072
  "areas.edit": "编辑领域",
15745
16073
  "common.edit": "编辑",
16074
+ "common.view": "查看",
15746
16075
  "common.add": "添加",
15747
16076
  "common.open": "打开",
15748
16077
  "common.ok": "确定",
@@ -16406,6 +16735,12 @@ App Store 已提供更新,是否立即打开应用页面?`,
16406
16735
  "settings.syncMobile.accountStatus": "账户状态",
16407
16736
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "请在 Dropbox OAuth 设置里添加以下精确回调地址。",
16408
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": "关闭",
16409
16744
  "settings.syncMobile.clearPendingAttachmentDeletes": "清除待处理的附件删除?",
16410
16745
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "这台设备上的 CloudKit 已被限制。请检查屏幕使用时间、设备管理或 iCloud 限制后再试。",
16411
16746
  "settings.syncMobile.connectedToDropbox": "已连接 Dropbox。",
@@ -16493,6 +16828,9 @@ App Store 已提供更新,是否立即打开应用页面?`,
16493
16828
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 端点可访问。",
16494
16829
  "settings.persistentCaptureLabel": "通知栏快速收集",
16495
16830
  "settings.persistentCaptureDesc": "保留常驻通知,随时随地(包括锁屏)快速收集。",
16831
+ "settings.exactAlarmsLabel": "提醒可能会延迟",
16832
+ "settings.exactAlarmsDesc": "Android 未允许 Mindwtr 设置精确闹钟,提醒可能会晚一分钟才响起。",
16833
+ "settings.exactAlarmsAllow": "允许",
16496
16834
  "settings.appSearchLabel": "在系统搜索中显示",
16497
16835
  "settings.appSearchDesc": "允许 Android 系统搜索按标题查找你的活动任务、项目和领域。数据不会离开此设备。",
16498
16836
  "captureNotification.title": "快速收集",
@@ -16827,6 +17165,8 @@ App Store 已提供更新,是否立即打开应用页面?`,
16827
17165
  "settings.gettingStartedContentContinueDesc": "完成此处的设置后,你仍可添加引导式“快速上手”项目和示例收集箱项目。",
16828
17166
  "settings.syncSetupGuideTitle": "数据与同步设置指南",
16829
17167
  "settings.syncSetupGuideDesc": "Dropbox、iCloud、WebDAV、文件同步和恢复的设置说明。",
17168
+ "settings.syncEncryptionGuideTitle": "同步加密指南",
17169
+ "settings.syncEncryptionGuideDesc": "它保护什么、哪些服务器支持它,以及口令如何工作。",
16830
17170
  "settings.importSetupGuideTitle": "导入设置指南",
16831
17171
  "settings.importSetupGuideDesc": "支持 Todoist、TickTick、DGT GTD、OmniFocus、Mindwtr CSV、Apple 提醒事项和备份导入路径。",
16832
17172
  "settings.backupDiagnostics.newerVersion": "此备份由较新版本的 Mindwtr({{version}})创建。",
@@ -16849,7 +17189,7 @@ App Store 已提供更新,是否立即打开应用页面?`,
16849
17189
  "settings.importDiagnostics.unmappedDate": "{{count}} 个日期值无法转换,已省略。",
16850
17190
  "settings.importDiagnostics.unmappedStatus": "{{count}} 个状态值无法转换,已使用安全默认值。",
16851
17191
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} 条不支持的重复规则已保留为备注。",
16852
- "settings.syncRemoteBusy": "另一台兼容的 Mindwtr 设备正在更新此同步位置。请等待其完成,然后再次同步。",
17192
+ "settings.syncRemoteBusy": "另一台 Mindwtr 设备暂时占用了此同步位置。同步将自动重试。",
16853
17193
  "settings.syncRemoteCleanupDeferred": "同步操作已完成。Mindwtr 无法移除临时同步锁,但该锁会自动过期。无需重试。",
16854
17194
  "settings.syncAttachmentWriteDeferred": "部分附件更改未能完成。请恢复缺失的本地文件或移除受影响的附件,然后再次同步。",
16855
17195
  "settings.syncFileAttachmentTooLarge": "Mindwtr 已保留本地附件。File Sync 只能同步小于 100 MB 的附件。请换用较小的文件或移除该附件,然后再次同步。",
@@ -16942,6 +17282,7 @@ var init_zh_Hant = __esm(() => {
16942
17282
  "bulk.keepStatus": "保留狀態",
16943
17283
  "bulk.keepProject": "保留項目",
16944
17284
  "bulk.keepArea": "保留領域",
17285
+ "bulk.keepSection": "保留分區",
16945
17286
  "bulk.waitingPersonRequired": "選擇這些事項在等待誰。",
16946
17287
  "bulk.deleting": "正在刪除所選任務…",
16947
17288
  "taskEdit.aiAssistant": "AI 助手",
@@ -16959,6 +17300,9 @@ var init_zh_Hant = __esm(() => {
16959
17300
  "nav.main": "主頁",
16960
17301
  "nav.inbox": "收集箱",
16961
17302
  "nav.board": "看板",
17303
+ "nav.timeline": "時間軸",
17304
+ "timeline.empty": "還沒有排期的任務",
17305
+ "timeline.emptyHint": "設定了開始日期或截止日期的任務會在這裡顯示為色條。",
16962
17306
  "nav.projects": "項目",
16963
17307
  "nav.contexts": "情境",
16964
17308
  "nav.next": "下一步行動",
@@ -17036,6 +17380,7 @@ var init_zh_Hant = __esm(() => {
17036
17380
  "keybindings.goReference": "前往參考",
17037
17381
  "keybindings.goCalendar": "前往日曆",
17038
17382
  "keybindings.goBoard": "前往看板",
17383
+ "keybindings.goTimeline": "前往時間軸",
17039
17384
  "keybindings.goDone": "前往已完成",
17040
17385
  "keybindings.goArchived": "前往歸檔",
17041
17386
  "keybindings.list.nextPrev": "上下移動選中項",
@@ -17190,9 +17535,12 @@ var init_zh_Hant = __esm(() => {
17190
17535
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
17191
17536
  "attachments.obsidianLinkInputHint": "貼上 obsidian:// 筆記連結,或使用「標題 | obsidian://...」。",
17192
17537
  "attachments.fileNotSupported": "文件附件僅在桌面應用中支持。",
17538
+ "attachments.webUnavailable": "此附件在網頁版中無法使用。",
17193
17539
  "attachments.fileTooLarge": "文件過大,無法上傳。",
17194
17540
  "attachments.fileNotReadable": "無法讀取此文件,因此未加入附件。請將文件移至其他資料夾後再試一次。",
17195
17541
  "attachments.linkToFile": "連結到文件…",
17542
+ "attachments.linkedFileElsewhere": "此連結指向另一台裝置上的檔案:{{path}}。請在該裝置上開啟,或改為直接加入該檔案作為附件。",
17543
+ "attachments.openLinkFailed": "無法開啟此連結。",
17196
17544
  "attachments.invalidFileType": "不支持的文件類型。",
17197
17545
  "attachments.invalidLink": "請輸入有效的鏈接。",
17198
17546
  "attachments.photoUnavailableTitle": "圖片選擇不可用",
@@ -17534,7 +17882,7 @@ var init_zh_Hant = __esm(() => {
17534
17882
  "projects.sectionPlaceholder": "分區標題",
17535
17883
  "projects.noSection": "無分區",
17536
17884
  "projects.sectionEmpty": "尚無任務",
17537
- "projects.deleteSectionConfirm": "確定要刪除此分區嗎?",
17885
+ "projects.deleteSectionConfirm": "刪除此分區?其中的任務不會被刪除,會移到「無分區」。",
17538
17886
  "projects.areaFilter": "領域篩選",
17539
17887
  "projects.allAreas": "所有領域",
17540
17888
  "projects.noArea": "無領域",
@@ -17561,6 +17909,8 @@ var init_zh_Hant = __esm(() => {
17561
17909
  "projects.complete": "完成",
17562
17910
  "projects.archive": "歸檔",
17563
17911
  "projects.reactivate": "重新激活",
17912
+ "projects.archivedTaskInspectionHint": "點兩下檢視此任務。重新激活專案後才能編輯。",
17913
+ "projects.archivedReadOnlyHint": "專案已歸檔。重新激活後才能編輯此任務。",
17564
17914
  "projects.actionsLabel": "操作",
17565
17915
  "projects.archiveHelp": "歸檔項目會將其和剩餘任務標記為完成,可隨時重新激活。",
17566
17916
  "projects.completeConfirm": "將此項目標記爲完成並結束其所有任務?",
@@ -17843,6 +18193,7 @@ var init_zh_Hant = __esm(() => {
17843
18193
  "settings.material3ThemeDesc": "在 Android 上使用 Material 3 配色",
17844
18194
  "settings.selectLang": "選擇您的首選語言",
17845
18195
  "settings.languagePartlyTranslated": "部分翻譯",
18196
+ "settings.videoTutorials": "影片教學",
17846
18197
  "settings.privacy": "隱私",
17847
18198
  "settings.mobile.appLock": "應用鎖",
17848
18199
  "settings.mobile.appLockDesc": "打開 Mindwtr 或返回應用時需要設備鎖驗證。它保護應用界面,不加密設備上的資料庫。",
@@ -17930,6 +18281,8 @@ var init_zh_Hant = __esm(() => {
17930
18281
  "settings.syncEncryptionUnlock": "輸入密碼短語",
17931
18282
  "settings.syncEncryptionDecline": "暫時不要",
17932
18283
  "settings.syncEncryptionPausedDesc": "在您輸入密碼短語之前,本裝置的自動同步會保持暫停。",
18284
+ "settings.syncEncryptionLockedRecheckHint": "如果該同步位置已不再存放加密檔案,請點按「立即同步」,本裝置會重新檢查該位置並繼續同步。",
18285
+ "settings.syncEncryptionNoEncryptedRemote": "該同步位置已沒有加密檔案,因此本裝置的加密現已關閉。如需加密該位置,請重新開啟。",
17933
18286
  "settings.syncEncryptionRemoteEncrypted": "此同步位置已加密。請輸入其同步密碼短語以繼續同步。",
17934
18287
  "settings.syncEncryptionRemotePlaintext": "同步已停止:此同步位置不再加密。請在此裝置上關閉同步加密,或在該同步位置重新啟用加密。",
17935
18288
  "settings.syncEncryptionRemotePlaintextDesc": "另一台裝置在此同步位置關閉了加密。此裝置上的內容沒有被變更或降級。若要以純文字繼續同步,請在此裝置上關閉同步加密;否則請在該同步位置重新啟用加密。",
@@ -18159,6 +18512,10 @@ var init_zh_Hant = __esm(() => {
18159
18512
  "settings.featureTimeEstimatesDesc": "爲時間管理添加時長估計。",
18160
18513
  "settings.featurePomodoro": "番茄鐘",
18161
18514
  "settings.featurePomodoroDesc": "在聚焦視圖中啓用可選的番茄鐘面板。",
18515
+ "settings.featureTimeline": "時間軸視圖",
18516
+ "settings.featureTimelineDesc": "在側邊欄顯示有日期任務的唯讀時間軸。",
18517
+ "settings.sidebarViews": "側邊欄視圖",
18518
+ "settings.sidebarViewsDesc": "選擇側邊欄中顯示哪些視圖。隱藏的視圖仍可透過搜尋開啟。",
18162
18519
  "settings.pomodoroCustomPreset": "自定義預設",
18163
18520
  "settings.pomodoroCustomPresetDesc": "添加一個額外的專注/休息預設。若與內建預設相同,將繼續只顯示內建選項。",
18164
18521
  "settings.pomodoroFocusMinutes": "專注分鐘",
@@ -18222,6 +18579,7 @@ var init_zh_Hant = __esm(() => {
18222
18579
  "tags.title": "標籤",
18223
18580
  "areas.edit": "編輯領域",
18224
18581
  "common.edit": "編輯",
18582
+ "common.view": "檢視",
18225
18583
  "common.add": "添加",
18226
18584
  "common.open": "打開",
18227
18585
  "common.ok": "確定",
@@ -18885,6 +19243,12 @@ App Store 已提供更新,是否立即打開應用頁面?`,
18885
19243
  "settings.syncMobile.accountStatus": "賬戶狀態",
18886
19244
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "請在 Dropbox OAuth 設置裡添加以下精確回調地址。",
18887
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": "關閉",
18888
19252
  "settings.syncMobile.clearPendingAttachmentDeletes": "清除待處理的附件刪除?",
18889
19253
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "這臺設備上的 CloudKit 已被限制。請檢查屏幕使用時間、設備管理或 iCloud 限制後再試。",
18890
19254
  "settings.syncMobile.connectedToDropbox": "已連接 Dropbox。",
@@ -18972,6 +19336,9 @@ App Store 已提供更新,是否立即打開應用頁面?`,
18972
19336
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 端點可訪問。",
18973
19337
  "settings.persistentCaptureLabel": "通知列快速收集",
18974
19338
  "settings.persistentCaptureDesc": "保留常駐通知,隨時隨地(包括鎖定畫面)快速收集。",
19339
+ "settings.exactAlarmsLabel": "提醒可能會延遲",
19340
+ "settings.exactAlarmsDesc": "Android 未允許 Mindwtr 設定精確鬧鐘,提醒可能會晚一分鐘才響起。",
19341
+ "settings.exactAlarmsAllow": "允許",
18975
19342
  "settings.appSearchLabel": "在系統搜尋中顯示",
18976
19343
  "settings.appSearchDesc": "允許 Android 系統搜尋依標題找到你的進行中任務、專案與領域。資料不會離開此裝置。",
18977
19344
  "captureNotification.title": "快速收集",
@@ -19306,6 +19673,8 @@ App Store 已提供更新,是否立即打開應用頁面?`,
19306
19673
  "settings.gettingStartedContentContinueDesc": "完成這裡的設定後,你仍可加入引導式「快速上手」專案和範例收集箱項目。",
19307
19674
  "settings.syncSetupGuideTitle": "資料與同步設定指南",
19308
19675
  "settings.syncSetupGuideDesc": "Dropbox、iCloud、WebDAV、檔案同步和還原的設定說明。",
19676
+ "settings.syncEncryptionGuideTitle": "同步加密指南",
19677
+ "settings.syncEncryptionGuideDesc": "它保護什麼、哪些伺服器支援它,以及密語如何運作。",
19309
19678
  "settings.importSetupGuideTitle": "匯入設定指南",
19310
19679
  "settings.importSetupGuideDesc": "支援 Todoist、TickTick、DGT GTD、OmniFocus、Mindwtr CSV、Apple 提醒事項和備份匯入路徑。",
19311
19680
  "settings.backupDiagnostics.newerVersion": "此備份由較新版本的 Mindwtr({{version}})建立。",
@@ -19328,7 +19697,7 @@ App Store 已提供更新,是否立即打開應用頁面?`,
19328
19697
  "settings.importDiagnostics.unmappedDate": "{{count}} 個日期值無法轉換,已省略。",
19329
19698
  "settings.importDiagnostics.unmappedStatus": "{{count}} 個狀態值無法轉換,已使用安全預設值。",
19330
19699
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} 條不支援的重複規則已保留為備註。",
19331
- "settings.syncRemoteBusy": "另一台相容的 Mindwtr 裝置正在更新此同步位置。請等待其完成,然後再次同步。",
19700
+ "settings.syncRemoteBusy": "另一台 Mindwtr 裝置暫時佔用了此同步位置。同步將自動重試。",
19332
19701
  "settings.syncRemoteCleanupDeferred": "同步作業已完成。Mindwtr 無法移除暫時同步鎖,但該鎖會自動失效。無需重試。",
19333
19702
  "settings.syncAttachmentWriteDeferred": "部分附件變更未能完成。請還原缺少的本機檔案或移除受影響的附件,然後再次同步。",
19334
19703
  "settings.syncFileAttachmentTooLarge": "Mindwtr 已保留本機附件。File Sync 只能同步小於 100 MB 的附件。請改用較小的檔案或移除該附件,然後再次同步。",
@@ -23838,7 +24207,7 @@ var init_de = __esm(() => {
23838
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").',
23839
24208
  "quickAdd.example": "Beispiel: Mama anrufen /due:tomorrow @phone",
23840
24209
  "quickAdd.inlineHint": "Tipp: Mama anrufen /due:tomorrow 5pm @phone #family",
23841
- "quickAdd.syntaxHelp": "Hilfe zur Schnell-hinzufügen-Syntax",
24210
+ "quickAdd.syntaxHelp": "Hilfe zur Schnell-Hinzufügen-Syntax",
23842
24211
  "quickAdd.placeholder": "Eine Aufgabe hinzufügen … benutzen Sie @context +Project #tag",
23843
24212
  "quickAdd.inputLabel": "Schnelle Eingabe",
23844
24213
  "quickAdd.inputHint": "Geben Sie eine Aufgabe ein und drücken Sie Eingabe zum Speichern.",
@@ -23879,7 +24248,7 @@ var init_de = __esm(() => {
23879
24248
  "quickAdd.audioSavingSpeechToText": "Aufnahme wird gespeichert und Sprache in Text umgewandelt.",
23880
24249
  "quickAdd.audioNoteTitle": "Sprachnotiz",
23881
24250
  "quickAdd.audioPermissionTitle": "Zugriff auf Mikrofon ist notwendig",
23882
- "quickAdd.audioPermissionBody": "Mikrofon-Zugriff erlauben, um Sprachnotizen aufzunehmen.",
24251
+ "quickAdd.audioPermissionBody": "Mikrofonzugriff erlauben, um Sprachnotizen aufzunehmen.",
23883
24252
  "quickAdd.audioErrorTitle": "Aufnahme fehlgeschlagen",
23884
24253
  "quickAdd.audioErrorBody": "Wir konnten nichts aufnehmen. Bitte versuchen Sie es nochmals.",
23885
24254
  "quickAdd.invalidDateCommand": "Ungültiger Datumsbefehl",
@@ -23904,7 +24273,7 @@ var init_de = __esm(() => {
23904
24273
  "keybindings.section.taskList": "Aufgabenliste",
23905
24274
  "keybindings.section.quickAddSyntax": "Schnelleingabe-Syntax",
23906
24275
  "keybindings.section.global": "Allgemein",
23907
- "keybindings.openHelp": "Taststaturkürzel anzeigen",
24276
+ "keybindings.openHelp": "Tastaturkürzel anzeigen",
23908
24277
  "keybindings.openSettings": "Einstellungen öffnen",
23909
24278
  "keybindings.toggleFullscreen": "Vollbild umschalten",
23910
24279
  "keybindings.switchArea": "Bereich wechseln",
@@ -23949,7 +24318,7 @@ var init_de = __esm(() => {
23949
24318
  "list.todo": "Zu tun",
23950
24319
  "list.inProgress": "In Arbeit",
23951
24320
  "list.next": "Nächste Aktionen",
23952
- "list.someday": "Irgendwann/Veilleicht",
24321
+ "list.someday": "Irgendwann/Vielleicht",
23953
24322
  "list.reference": "Referenz",
23954
24323
  "list.waiting": "Abwarten",
23955
24324
  "list.done": "Abgeschlossen",
@@ -23967,7 +24336,7 @@ var init_de = __esm(() => {
23967
24336
  "list.densityComfortable": "Komfortabel",
23968
24337
  "list.densityCompact": "Kompakt",
23969
24338
  "list.densityCondensed": "Verdichtet",
23970
- "reference.empty": "Noch keine Referenz-Einträge.",
24339
+ "reference.empty": "Noch keine Referenzeinträge.",
23971
24340
  "status.inbox": "Posteingang",
23972
24341
  "status.todo": "Zu tun",
23973
24342
  "status.next": "Nächstes",
@@ -24005,7 +24374,7 @@ var init_de = __esm(() => {
24005
24374
  "taskEdit.duplicateDoneBody": "Es wurde eine neue Kopie im Posteingang erstellt.",
24006
24375
  "taskEdit.aiClarify": "Klären mit KI",
24007
24376
  "taskEdit.aiBreakdown": "KI-Zusammenfassung",
24008
- "taskEdit.itemNamePlaceholder": "Elementen-Name",
24377
+ "taskEdit.itemNamePlaceholder": "Elementname",
24009
24378
  "taskEdit.titleLabel": "Titel",
24010
24379
  "taskEdit.editorLayoutHelpLabel": "Editor-Layout-Hilfe",
24011
24380
  "taskEdit.editorLayoutHelpText": "Du kannst in Einstellungen -> GTD -> Aufgaben-Editor-Layout anpassen, welche Felder hier angezeigt werden.",
@@ -24038,7 +24407,7 @@ var init_de = __esm(() => {
24038
24407
  "markdown.collapse": "Zusammenklappen",
24039
24408
  "markdown.toolbar.heading": "Titel einfügen",
24040
24409
  "markdown.toolbar.bold": "Fett",
24041
- "markdown.toolbar.italic": "Schrägschrift",
24410
+ "markdown.toolbar.italic": "Kursiv",
24042
24411
  "markdown.toolbar.strikethrough": "Durchgestrichen",
24043
24412
  "markdown.toolbar.bulletList": "Punkteliste",
24044
24413
  "markdown.toolbar.horizontalRule": "Horizontale Linie",
@@ -24055,7 +24424,7 @@ var init_de = __esm(() => {
24055
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.",
24056
24425
  "attachments.unrecoverable": "Dieser Anhang ist im synchronisierten Speicher nicht mehr verfügbar. Der ungültige Verweis wurde entfernt.",
24057
24426
  "attachments.remove": "Entfernen",
24058
- "attachments.transferProgress": "Fortschritt der Anhangsübertragung",
24427
+ "attachments.transferProgress": "Fortschritt der Übertragung von Anhängen",
24059
24428
  "attachments.linkPlaceholder": "https://beispiel.com",
24060
24429
  "attachments.linkInputHint": 'Tipp: eine URL einfügen, oder "Titel | URL" benutzen.',
24061
24430
  "attachments.attachObsidianNote": "Obsidian-Notiz anhängen",
@@ -24065,12 +24434,12 @@ var init_de = __esm(() => {
24065
24434
  "attachments.fileTooLarge": "Die Datei ist zu groß für das Hochladen.",
24066
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.",
24067
24436
  "attachments.linkToFile": "Mit Datei verknüpfen…",
24068
- "attachments.invalidFileType": "Nicht unterstützter Dateientyp.",
24437
+ "attachments.invalidFileType": "Nicht unterstützter Dateityp.",
24069
24438
  "attachments.invalidLink": "Bitte eine gültige URL angeben.",
24070
- "attachments.photoUnavailableTitle": "Foto-Auswahl ist nicht verfügbar",
24439
+ "attachments.photoUnavailableTitle": "Fotoauswahl ist nicht verfügbar",
24071
24440
  "attachments.photoUnavailableBody": "Die App neu erstellen, um Fotoanhänge zu ermöglichen.",
24072
24441
  "taskEdit.locationLabel": "Standort",
24073
- "taskEdit.locationPlaceholder": "z.B. Büro",
24442
+ "taskEdit.locationPlaceholder": "z. B. Büro",
24074
24443
  "taskEdit.projectLabel": "Projekt",
24075
24444
  "taskEdit.noProjectOption": "Kein Projekt",
24076
24445
  "taskEdit.sectionLabel": "Abschnitt",
@@ -24080,7 +24449,7 @@ var init_de = __esm(() => {
24080
24449
  "taskEdit.moreOptions": "Mehr Optionen",
24081
24450
  "taskEdit.hideOptions": "Optionen verstecken",
24082
24451
  "taskEdit.startDateLabel": "Anfangsdatum",
24083
- "taskEdit.dueDateLabel": "Fälligekeitsdatum",
24452
+ "taskEdit.dueDateLabel": "Fälligkeitsdatum",
24084
24453
  "taskEdit.reviewDateLabel": "Revisions-Datum",
24085
24454
  "taskEdit.dateOnly": "Nur Datum",
24086
24455
  "taskEdit.startModeLabel": "Startmodus",
@@ -24157,16 +24526,16 @@ var init_de = __esm(() => {
24157
24526
  "recurrence.ordinal.second": "Zweiten",
24158
24527
  "recurrence.ordinal.third": "Dritten",
24159
24528
  "recurrence.ordinal.fourth": "Vierten",
24160
- "recurrence.ordinal.last": "Letzen",
24529
+ "recurrence.ordinal.last": "Letzten",
24161
24530
  "recurrence.monthlyOnDay": "Der gleiche Tag jeden Monats",
24162
24531
  "recurrence.monthlyOnLastWeekday": "Letzter {weekday}",
24163
- "recurrence.strategyLabel": "Stratgisch",
24532
+ "recurrence.strategyLabel": "Strategisch",
24164
24533
  "recurrence.strategyStrict": "Strikt",
24165
24534
  "recurrence.strategyFluid": "Fliessend",
24166
- "recurrence.strategyStrictDesc": "Fällig am geplanten Datum (z.B. Rechnungen)",
24167
- "recurrence.strategyFluidDesc": "Fällig nach Abschluss (z.B. Wäsche)",
24168
- "recurrence.afterCompletion": "Nach Abschluß wiederholen",
24169
- "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",
24170
24539
  "recurrence.showFutureInCalendar": "Künftige Wiederholungen im Kalender anzeigen",
24171
24540
  "recurrence.showFutureInCalendarHint": "Nur Planungsvorschau; die nächste echte Aufgabe wird erst erstellt, wenn diese abgeschlossen wird.",
24172
24541
  "inbox.title": "Posteingang",
@@ -24180,7 +24549,7 @@ var init_de = __esm(() => {
24180
24549
  "inbox.refineHint": "Präzisiere den Titel und die Details, bevor Sie entscheiden, was als Nächstes getan werden soll.",
24181
24550
  "inbox.refineNext": "Nächstes",
24182
24551
  "inbox.refineDelete": "Löschen",
24183
- "inbox.isActionable": "Ist dies ausführbar?",
24552
+ "inbox.isActionable": "Ist diese ausführbar?",
24184
24553
  "inbox.actionableHint": "Können Sie eine physische Handlung ausführen?",
24185
24554
  "inbox.yes": "Ja",
24186
24555
  "inbox.no": "Nein",
@@ -24201,7 +24570,7 @@ var init_de = __esm(() => {
24201
24570
  "inbox.addContextPlaceholder": "Neuen Kontext hinzufügen ...",
24202
24571
  "inbox.waitingQuestion": "Auf wen oder was warten Sie?",
24203
24572
  "inbox.waitingHint": "Fügen Sie eine Notiz hinzu, um sich zu erinnern, auf was Sie warten",
24204
- "inbox.waitingPlaceholder": "Z.B. Auf Jonathan's Revision warten ...",
24573
+ "inbox.waitingPlaceholder": "Z. B. Auf Jonathan's Revision warten ...",
24205
24574
  "inbox.assignProjectQuestion": "Zu einem Projekt hinzufügen? (Optional)",
24206
24575
  "inbox.noProject": "Kein Projekt",
24207
24576
  "inbox.skip": "Überspringen",
@@ -24213,7 +24582,7 @@ var init_de = __esm(() => {
24213
24582
  "next.noTasks": "Keine Aufgaben in Zu Tun. Fügen Sie sie in den Posteingang ein und verarbeiten Sie diese zuerst.",
24214
24583
  "next.noContext": "Keine Nächsten Aktionen mit",
24215
24584
  "next.warningCount": "Elemente in Nächste Aktionen",
24216
- "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.",
24217
24586
  "contexts.title": "Kontexte",
24218
24587
  "contexts.filter": "Aufgaben nach Kontexten filtern",
24219
24588
  "filters.label": "Filter",
@@ -24243,7 +24612,7 @@ var init_de = __esm(() => {
24243
24612
  "filters.noMatch": " Diesem Filter entsprechen keine Aufgaben.",
24244
24613
  "contexts.all": "Alle Kontexte",
24245
24614
  "contexts.none": "Keine Kontexte",
24246
- "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.",
24247
24616
  "contexts.noTasks": "Keine aktiven Aufgaben für diesen Kontext",
24248
24617
  "board.title": "Tafelansicht",
24249
24618
  "board.next": "Nächste Aktionen",
@@ -24251,11 +24620,11 @@ var init_de = __esm(() => {
24251
24620
  "board.inProgress": "In Arbeit",
24252
24621
  "board.done": "Erledigt",
24253
24622
  "board.noTasks": "Keine Aufgaben",
24254
- "board.hint": "Halten zum Verschieben • Nach links swipen zum Löschen",
24623
+ "board.hint": "Halten zum Verschieben • Zum Löschen nach links wischen",
24255
24624
  "board.dragTask": "Aufgabe verschieben",
24256
24625
  "board.delete": "Löschen",
24257
24626
  "calendar.title": "Kalender",
24258
- "calendar.addTask": "Neue Aufgabe hinzfügen ...",
24627
+ "calendar.addTask": "Neue Aufgabe hinzufügen ...",
24259
24628
  "calendar.schedulePlaceholder": "Aufgaben suchen zum Einplanen ...",
24260
24629
  "calendar.scheduleResults": "Zeitplan",
24261
24630
  "calendar.scheduleAction": "Zeitplan",
@@ -24313,10 +24682,10 @@ var init_de = __esm(() => {
24313
24682
  "project.notes": "Projektnotizen",
24314
24683
  "projects.notesPlaceholder": "Kontexte, Pläne oder Referenzen zu diesem Projekt hinzufügen ...",
24315
24684
  "projects.sectionNotes": "Abschnitts-Notizen",
24316
- "projects.sectionNotesPlaceholder": "Notizen zu diesem Abschnitt hinzfügen ...",
24317
- "projects.reviewAt": "Datum revisieren",
24685
+ "projects.sectionNotesPlaceholder": "Notizen zu diesem Abschnitt hinzufügen ...",
24686
+ "projects.reviewAt": "Revisionsdatum",
24318
24687
  "projects.areaLabel": "Bereich",
24319
- "projects.areaPlaceholder": "Z.B. Arbeit",
24688
+ "projects.areaPlaceholder": "Z. B. Arbeit",
24320
24689
  "projects.sectionsLabel": "Abschnitte",
24321
24690
  "projects.addSection": "Abschnitt hinzufügen",
24322
24691
  "projects.sectionPlaceholder": "Titel für den Abschnitt",
@@ -24369,7 +24738,7 @@ var init_de = __esm(() => {
24369
24738
  "dailyReview.todayStep": "Heute & Kalender",
24370
24739
  "dailyReview.todayDesc": "Überprüfen, was heute ansteht und welche Verpflichtungen im Kalender stehen.",
24371
24740
  "dailyReview.focusStep": "Heutiger Fokus",
24372
- "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.",
24373
24742
  "dailyReview.inboxStep": "Posteingang verarbeiten",
24374
24743
  "dailyReview.inboxDesc": "Neue Eingaben klären in der Rechte-Liste.",
24375
24744
  "dailyReview.waitingStep": "Warten auf",
@@ -24392,9 +24761,9 @@ var init_de = __esm(() => {
24392
24761
  "review.calendarStep": "Kalender-Revision",
24393
24762
  "review.calendarStepDesc": "Revidieren Sie Ihren Kalender für die nächste 7 Tage.",
24394
24763
  "review.past14": "Letzte 14 Tage",
24395
- "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?",
24396
24765
  "review.upcoming14": "Nächste 7 Tage",
24397
- "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.",
24398
24767
  "review.waitingStep": "Warten auf",
24399
24768
  "review.waitingStepDesc": "Verfolgen Sie die delegierten Aufgaben.",
24400
24769
  "review.waitingHint": "Revidieren Sie diese Elemente. Haben Sie erhalten, auf was Sie warten? Müssen Sie eine Erinnerung absenden?",
@@ -24404,8 +24773,8 @@ var init_de = __esm(() => {
24404
24773
  "review.staleStep": "Liegengebliebene Aufgaben",
24405
24774
  "review.staleStepDesc": "Keine Aktivität in letzter Zeit. Aktualisieren, abschließen oder loslassen.",
24406
24775
  "review.staleDaysInactive": "{{days}} Tage inaktiv",
24407
- "review.advanceWeek": "In 1 Woche prüfen",
24408
- "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",
24409
24778
  "review.aiStep": "KI-Einblick",
24410
24779
  "review.aiStepDesc": "Veraltete Elemente hervorheben und Bereinigungsvorschläge anzeigen.",
24411
24780
  "review.aiTitle": "KI-Revision",
@@ -24413,14 +24782,14 @@ var init_de = __esm(() => {
24413
24782
  "review.aiRunning": "Analysieren ...",
24414
24783
  "review.aiEmpty": "Keine veralteten Elemente gefunden.",
24415
24784
  "review.aiApply": "Ausgewählte anwenden",
24416
- "review.aiAction.someday": "Nach Irgedendwann verschieben",
24785
+ "review.aiAction.someday": "Nach Irgendwann verschieben",
24417
24786
  "review.aiAction.archive": "Archiv",
24418
24787
  "review.aiAction.breakdown": "Benötigt eine Aufschlüsselung",
24419
24788
  "review.aiAction.keep": "Behalten",
24420
24789
  "review.notDueYet": "Noch nicht fällig",
24421
24790
  "review.projectsStep": "Projekte revidieren",
24422
24791
  "review.projectsStepDesc": "Sicherstellen, dass jedes aktive Projekt eine nächste Aktion enthält.",
24423
- "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.",
24424
24793
  "review.hasNextAction": "Enthält eine nächste Aktion",
24425
24794
  "review.needsAction": "Benötigt eine nächste Aktion",
24426
24795
  "review.noActiveTasks": "Keine aktiven Aufgaben",
@@ -24431,7 +24800,7 @@ var init_de = __esm(() => {
24431
24800
  "review.allDone": "Alles erledigt!",
24432
24801
  "review.allDoneDesc": "Sie sind für bereit für die kommende Woche.",
24433
24802
  "review.complete": "Revision abgeschlossen!",
24434
- "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.",
24435
24804
  "review.summaryInboxEmpty": "Eingang ist leer",
24436
24805
  "review.summaryInboxCount": "{{count}} Element(e) noch im Eingang",
24437
24806
  "review.summaryProjectsOk": "Jedes aktive Projekt hat eine nächste Aktion",
@@ -24474,10 +24843,10 @@ var init_de = __esm(() => {
24474
24843
  "process.nextStepDesc": "Sollen Sie es tun oder an jemanden delegieren?",
24475
24844
  "process.doIt": "\uD83D\uDCCB Ich werde es selber erledigen",
24476
24845
  "process.delegate": "Delegieren",
24477
- "process.delegateTitle": "Delegierung",
24846
+ "process.delegateTitle": "Delegieren",
24478
24847
  "process.delegateDesc": "Optional: Notieren Sie, wer beteiligt ist, und einen Termin für die Weiterverfolgung.",
24479
24848
  "process.delegateWhoLabel": "Wer? (Optional)",
24480
- "process.delegateWhoPlaceholder": "Z.B. Alex",
24849
+ "process.delegateWhoPlaceholder": "Z. B. Alex",
24481
24850
  "process.delegateFollowUpLabel": "Weiterverfolgungs-Termin (Optional)",
24482
24851
  "process.delegateSendRequest": "Anfrage senden ...",
24483
24852
  "process.delegateMoveToWaiting": "Nach Warten verschieben",
@@ -24488,7 +24857,7 @@ var init_de = __esm(() => {
24488
24857
  "process.ifNotActionable": "Wenn es nicht ausführbar ist:",
24489
24858
  "process.waitingFor": "Auf wen oder was warten Sie?",
24490
24859
  "process.waitingForDesc": "Fügen Sie eine Notiz hinzu, um sich zu erinnern, auf was Sie warten",
24491
- "process.waitingPlaceholder": "Z.B. Auf Jonathan's Revision des Dokumentes warten ...",
24860
+ "process.waitingPlaceholder": "Z. B. Auf Jonathan's Revision des Dokumentes warten ...",
24492
24861
  "process.next": "Nächste",
24493
24862
  "process.noContext": "Kein Kontext",
24494
24863
  "process.project": "Zu einem Projekt hinzufügen?",
@@ -24538,7 +24907,7 @@ var init_de = __esm(() => {
24538
24907
  "settings.calendarSystemJalali": "Jalali (Sonnen-Hidschra)",
24539
24908
  "settings.keybindingsDesc": "Tastaturbelegung-Stil für den Desktop auswählen.",
24540
24909
  "settings.closeBehaviorDesc": "Wählen Sie, was beim Schließen des Fensters passieren soll.",
24541
- "settings.closeBehaviorAsk": "Jedesmal fragen",
24910
+ "settings.closeBehaviorAsk": "Jedes Mal fragen",
24542
24911
  "settings.closeBehaviorTray": "Im Taskbereich weiterlaufen lassen",
24543
24912
  "settings.closeBehaviorQuit": "Die App schließen",
24544
24913
  "settings.closeBehaviorPromptTitle": "Mindwtr schließen?",
@@ -24677,14 +25046,14 @@ var init_de = __esm(() => {
24677
25046
  "settings.aiApiKeyPlaceholder": "API-Schlüssel einfügen",
24678
25047
  "settings.speechTitle": "Sprache zu Text",
24679
25048
  "settings.speechDesc": "Sprachaufnahmen transkribieren und sie in Aufgabenfelder einteilen.",
24680
- "settings.speechEnable": "Sprache zu Text aktiviern",
25049
+ "settings.speechEnable": "Sprache zu Text aktivieren",
24681
25050
  "settings.speechProvider": "Spracherkennungs-Provider",
24682
25051
  "settings.speechProviderOffline": "Auf dem Gerät (Whisper)",
24683
25052
  "settings.speechModel": "Sprachmodell",
24684
25053
  "settings.speechBaseUrl": "Transkriptionsserver-URL",
24685
25054
  "settings.speechBaseUrlHint": "Für offizielles OpenAI leer lassen. Für einen selbst gehosteten OpenAI-kompatiblen Transkriptionsserver festlegen. API-Schlüssel optional.",
24686
25055
  "settings.speechOfflineModel": "Offline Modell",
24687
- "settings.speechOfflineModelDesc": "Einmal herunterlanden, um dann völlig offline zu transkribieren.",
25056
+ "settings.speechOfflineModelDesc": "Einmal herunterladen, um dann völlig offline zu transkribieren.",
24688
25057
  "settings.speechOfflineReady": "Modell heruntergeladen",
24689
25058
  "settings.speechOfflineNotDownloaded": "Modell nicht heruntergeladen",
24690
25059
  "settings.speechOfflineDownload": "Herunterladen",
@@ -24694,13 +25063,13 @@ var init_de = __esm(() => {
24694
25063
  "settings.speechOfflineDeleteError": "Offline-Modell löschen ist fehlgeschlagen",
24695
25064
  "settings.speechOfflineDeleteErrorBody": "Bitte nochmals versuchen.",
24696
25065
  "settings.speechLanguage": "Audio-Sprache",
24697
- "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.",
24698
25067
  "settings.speechLanguageAuto": "Automatisch (Sprache bestimmen)",
24699
25068
  "settings.speechMode": "Verarbeitungs-Modus",
24700
- "settings.speechModeHint": "Smart parse extrahiert automatisch Felder und Daten; Nur Transkripieren transkripiert einfach.",
24701
- "settings.speechModeTranscript": "Nur Transkripieren",
25069
+ "settings.speechModeHint": "'Smart parse' extrahiert automatisch Felder und Daten; 'Nur Transkribieren' transkribiert lediglich.",
25070
+ "settings.speechModeTranscript": "Nur Transkribieren",
24702
25071
  "settings.speechFieldStrategy": "Felder bestimmen",
24703
- "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.",
24704
25073
  "settings.speechFieldTitle": "Titel",
24705
25074
  "settings.speechFieldDescription": "Beschreibung",
24706
25075
  "settings.aiReasoning": "Begründungsaufwand",
@@ -24861,8 +25230,8 @@ var init_de = __esm(() => {
24861
25230
  "sort.label": "Sortieren",
24862
25231
  "sort.default": "Standard",
24863
25232
  "sort.due": "Fälligkeitsdatum",
24864
- "sort.start": "Beginn-Datum",
24865
- "sort.review": "Revisions-Datum",
25233
+ "sort.start": "Beginndatum",
25234
+ "sort.review": "Revisionsdatum",
24866
25235
  "sort.title": "Titel",
24867
25236
  "sort.timeEstimate": "Zeitabschätzung",
24868
25237
  "sort.created": "Älteste",
@@ -24885,7 +25254,7 @@ var init_de = __esm(() => {
24885
25254
  "agenda.focusHint": "Tippen Sie auf den Stern irgendeiner untenstehenden Aufgabe, um sie zum heutigen Fokus hinzuzufügen (max. 3).",
24886
25255
  "agenda.addToFocus": "Zum heutigen Fokus hinzufügen",
24887
25256
  "agenda.removeFromFocus": "Aus dem Fokus entfernen",
24888
- "agenda.maxFocusItems": "Max. 3 Fokus-Elemente",
25257
+ "agenda.maxFocusItems": "Max. {{count}} Fokus-Element(e)",
24889
25258
  "agenda.inProgress": "In Arbeit",
24890
25259
  "agenda.overdue": "Überfällig",
24891
25260
  "agenda.dueToday": "Heute fällig",
@@ -24915,7 +25284,7 @@ var init_de = __esm(() => {
24915
25284
  "waiting.moveToNext": "Nach Nächste verschieben",
24916
25285
  "waiting.markDone": "Als Erledigt markieren",
24917
25286
  "waiting.empty": "Es gibt nichts, worauf Sie warten müssten",
24918
- "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",
24919
25288
  "someday.title": "Irgendwann/Vielleicht",
24920
25289
  "someday.subtitle": "Ideen und Ziele, die Sie in Zukunft vielleicht verfolgen möchten",
24921
25290
  "someday.ideas": "Ideen",
@@ -24923,7 +25292,7 @@ var init_de = __esm(() => {
24923
25292
  "someday.moveToNext": "Zu Nächste verschieben",
24924
25293
  "someday.archive": "Archiv",
24925
25294
  "someday.empty": "Keine Irgendwann/Vielleicht-Elemente",
24926
- "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",
24927
25296
  "search.title": "Suchen",
24928
25297
  "search.placeholder": "Aufgaben and Projekte suchen ...",
24929
25298
  "search.scopeHint": "Aufgaben, Projekte, Personen",
@@ -24936,10 +25305,10 @@ var init_de = __esm(() => {
24936
25305
  "search.showingFirst": "Zeigt {shown} von {total} Ergebnissen",
24937
25306
  "search.saveSearch": "Diese Suchabfrage speichern",
24938
25307
  "search.saveSearchPrompt": "Diese Suchabfrage benennen",
24939
- "search.savedSearches": "Sucheabfrage gespeichert",
25308
+ "search.savedSearches": "Suchabfrage gespeichert",
24940
25309
  "search.noSavedSearches": "Noch keine gespeicherte Suchabfragen.",
24941
25310
  "search.deleteConfirm": "Diese gespeicherte Suchabfrage löschen?",
24942
- "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.",
24943
25312
  "search.hiddenCompletedMatches": "{{count}} weitere in Erledigt und Archiviert",
24944
25313
  "search.completedDate": "Abgeschlossen {{date}}",
24945
25314
  "search.dueDate": "Fällig {{date}}",
@@ -25008,7 +25377,7 @@ var init_de = __esm(() => {
25008
25377
  "settings.sync": "Synchronisieren",
25009
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.",
25010
25379
  "settings.attachmentsCleanup": "Bereinigung von Anhängen",
25011
- "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.",
25012
25381
  "settings.attachmentsCleanupLastRun": "Letzte Bereinigung",
25013
25382
  "settings.attachmentsCleanupNever": "Nie",
25014
25383
  "settings.attachmentsCleanupRun": "Führen Sie die Bereinigung durch",
@@ -25037,7 +25406,7 @@ var init_de = __esm(() => {
25037
25406
  "settings.cloudProviderDropbox": "Dropbox",
25038
25407
  "settings.dropboxAppKey": "Dropbox-Konto",
25039
25408
  "settings.dropboxAppKeyHint": "Der Dropbox-App-Schlüssel wird zum Zeitpunkt der Erstellung/Veröffentlichung eingefügt.",
25040
- "settings.dropboxRedirectUri": "Umleitungs-URI",
25409
+ "settings.dropboxRedirectUri": "Redirect-URI",
25041
25410
  "settings.dropboxConnected": "Verbunden",
25042
25411
  "settings.dropboxNotConnected": "Nicht verbunden",
25043
25412
  "settings.dropboxConnect": "Dropbox verbinden",
@@ -25090,7 +25459,7 @@ var init_de = __esm(() => {
25090
25459
  "settings.downloadStarting": "Download wird geöffnet ...",
25091
25460
  "settings.downloadStarted": "Der Download wurde in Ihrem Browser gestartet.",
25092
25461
  "settings.downloadFailed": "Der Download-Link konnte nicht geöffnet werden.",
25093
- "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.",
25094
25463
  "settings.downloadRecommended": "Empfohlenes Paket",
25095
25464
  "settings.downloadAURHint": "Arch erkannt: Aktualisierung über AUR",
25096
25465
  "settings.changelog": "Änderungsprotokoll",
@@ -26844,6 +27213,9 @@ var init_ja = __esm(() => {
26844
27213
  "nav.main": "メイン",
26845
27214
  "nav.inbox": "インボックス",
26846
27215
  "nav.board": "ボードビュー",
27216
+ "nav.timeline": "タイムライン",
27217
+ "timeline.empty": "予定されたタスクはまだありません",
27218
+ "timeline.emptyHint": "開始日または期限日のあるタスクがバーとして表示されます。",
26847
27219
  "nav.projects": "プロジェクト",
26848
27220
  "nav.contexts": "コンテキスト",
26849
27221
  "nav.next": "次のアクション",
@@ -26930,6 +27302,7 @@ var init_ja = __esm(() => {
26930
27302
  "keybindings.goReference": "資料へ移動",
26931
27303
  "keybindings.goCalendar": "カレンダーへ移動",
26932
27304
  "keybindings.goBoard": "ボードへ移動",
27305
+ "keybindings.goTimeline": "タイムラインへ移動",
26933
27306
  "keybindings.goDone": "完了へ移動",
26934
27307
  "keybindings.goArchived": "アーカイブへ移動",
26935
27308
  "keybindings.switchArea": "エリア 1〜9 に切り替え",
@@ -27107,9 +27480,12 @@ var init_ja = __esm(() => {
27107
27480
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
27108
27481
  "attachments.obsidianLinkInputHint": "obsidian:// のノートリンクを貼り付けるか、「タイトル | obsidian://…」の形式で入力してください。",
27109
27482
  "attachments.fileNotSupported": "ファイルの添付はデスクトップ版でのみ利用できます。",
27483
+ "attachments.webUnavailable": "この添付ファイルはウェブ版では利用できません。",
27110
27484
  "attachments.fileTooLarge": "ファイルが大きすぎてアップロードできません。",
27111
27485
  "attachments.fileNotReadable": "このファイルを読み取れなかったため添付できませんでした。別のフォルダに移してからもう一度お試しください。",
27112
27486
  "attachments.linkToFile": "ファイルへのリンク…",
27487
+ "attachments.linkedFileElsewhere": "このリンクは別のデバイス上のファイルを指しています: {{path}}。そのデバイスで開くか、リンクではなくファイルを添付してください。",
27488
+ "attachments.openLinkFailed": "このリンクを開けませんでした。",
27113
27489
  "attachments.invalidFileType": "対応していないファイル形式です。",
27114
27490
  "attachments.invalidLink": "有効な URL を入力してください。",
27115
27491
  "attachments.photoUnavailableTitle": "写真の選択が利用できません",
@@ -27477,7 +27853,7 @@ var init_ja = __esm(() => {
27477
27853
  "projects.sectionPlaceholder": "セクション名",
27478
27854
  "projects.noSection": "セクションなし",
27479
27855
  "projects.sectionEmpty": "タスクなし",
27480
- "projects.deleteSectionConfirm": "このセクションを削除してもよろしいですか?",
27856
+ "projects.deleteSectionConfirm": "このセクションを削除しますか?中のタスクは削除されず、「セクションなし」に移動します。",
27481
27857
  "projects.areaFilter": "エリアで絞り込む",
27482
27858
  "projects.allAreas": "すべてのエリア",
27483
27859
  "projects.noArea": "エリアなし",
@@ -27510,6 +27886,8 @@ var init_ja = __esm(() => {
27510
27886
  "projects.complete": "完了にする",
27511
27887
  "projects.archive": "アーカイブ",
27512
27888
  "projects.reactivate": "再開する",
27889
+ "projects.archivedTaskInspectionHint": "ダブルタップでこのタスクを確認できます。編集するにはプロジェクトを再開してください。",
27890
+ "projects.archivedReadOnlyHint": "アーカイブ済みのプロジェクトです。このタスクを編集するには再開してください。",
27513
27891
  "projects.actionsLabel": "操作",
27514
27892
  "projects.archiveHelp": "アーカイブすると、プロジェクトと残っているタスクがまとめて完了になります。あとからいつでも再開できます。",
27515
27893
  "projects.completeConfirm": "このプロジェクトを完了にして、含まれるタスクもすべて終わらせますか?",
@@ -27815,6 +28193,7 @@ var init_ja = __esm(() => {
27815
28193
  "settings.material3ThemeDesc": "Android で Material 3 の配色を使います",
27816
28194
  "settings.selectLang": "使用する言語を選んでください",
27817
28195
  "settings.languagePartlyTranslated": "一部のみ翻訳",
28196
+ "settings.videoTutorials": "動画チュートリアル",
27818
28197
  "settings.privacy": "プライバシー",
27819
28198
  "settings.mobile.appLock": "アプリロック",
27820
28199
  "settings.mobile.appLockDesc": "Mindwtr を開くとき、またはアプリに戻るときに端末のロック解除を求めます。保護されるのは画面の表示だけで、端末内のデータベースそのものは保護の対象ではありません。",
@@ -27902,6 +28281,8 @@ var init_ja = __esm(() => {
27902
28281
  "settings.syncEncryptionUnlock": "パスフレーズを入力",
27903
28282
  "settings.syncEncryptionDecline": "後で",
27904
28283
  "settings.syncEncryptionPausedDesc": "パスフレーズを入力するまで、この端末の自動同期は停止したままになります。",
28284
+ "settings.syncEncryptionLockedRecheckHint": "この同期先に暗号化されたファイルがもうない場合は、「今すぐ同期」をタップしてください。この端末が同期先を再確認して続行します。",
28285
+ "settings.syncEncryptionNoEncryptedRemote": "この同期先には暗号化されたファイルがなくなったため、この端末の暗号化はオフになりました。この場所を暗号化するには、もう一度オンにしてください。",
27905
28286
  "settings.syncEncryptionRemoteEncrypted": "この同期先は暗号化されています。同期を続けるには、同期パスフレーズを入力してください。",
27906
28287
  "settings.syncEncryptionRemotePlaintext": "同期を停止しました: この同期先はもう暗号化されていません。このデバイスで同期の暗号化をオフにするか、同期先で暗号化をもう一度オンにしてください。",
27907
28288
  "settings.syncEncryptionRemotePlaintextDesc": "別のデバイスがこの同期先の暗号化をオフにしました。この端末側では何も変更も解除もされていません。平文のまま同期を続けるにはこのデバイスで同期の暗号化をオフにし、そうでなければ同期先で暗号化をもう一度オンにしてください。",
@@ -28147,6 +28528,10 @@ var init_ja = __esm(() => {
28147
28528
  "settings.featureTimeEstimatesDesc": "時間を確保して計画を立てるための、簡単な所要時間の見積もりを追加します。",
28148
28529
  "settings.featurePomodoro": "ポモドーロタイマー",
28149
28530
  "settings.featurePomodoroDesc": "フォーカス画面にポモドーロのパネルを表示します。",
28531
+ "settings.featureTimeline": "タイムラインビュー",
28532
+ "settings.featureTimelineDesc": "日付のあるタスクの読み取り専用タイムラインをサイドバーに表示します。",
28533
+ "settings.sidebarViews": "サイドバーの表示項目",
28534
+ "settings.sidebarViewsDesc": "サイドバーに表示するビューを選択します。非表示のビューも検索から開けます。",
28150
28535
  "settings.pomodoroCustomPreset": "カスタムのプリセット",
28151
28536
  "settings.pomodoroCustomPresetDesc": "集中・休憩の組み合わせを1つだけ追加できます。既定のプリセットと同じ値にした場合は、既定のものだけが表示されます。",
28152
28537
  "settings.pomodoroFocusMinutes": "集中する時間(分)",
@@ -28233,6 +28618,7 @@ var init_ja = __esm(() => {
28233
28618
  "tags.title": "タグ",
28234
28619
  "areas.edit": "エリアを編集",
28235
28620
  "common.edit": "編集",
28621
+ "common.view": "表示",
28236
28622
  "common.add": "追加",
28237
28623
  "common.open": "開く",
28238
28624
  "common.ok": "OK",
@@ -28300,6 +28686,7 @@ var init_ja = __esm(() => {
28300
28686
  "bulk.keepStatus": "ステータスは変えない",
28301
28687
  "bulk.keepProject": "プロジェクトは変えない",
28302
28688
  "bulk.keepArea": "エリアは変えない",
28689
+ "bulk.keepSection": "セクションは変えない",
28303
28690
  "bulk.waitingPersonRequired": "待っている相手を選んでください。",
28304
28691
  "bulk.deleting": "選択したタスクを削除しています…",
28305
28692
  "sort.label": "並び順",
@@ -28845,6 +29232,12 @@ App Store に更新があります。アプリのページを開きますか?`
28845
29232
  "settings.syncMobile.accountStatus": "アカウントの状態",
28846
29233
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Dropbox の OAuth 設定に、このリダイレクト URI をそのまま登録してください。",
28847
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": "オフ",
28848
29241
  "settings.syncMobile.clearPendingAttachmentDeletes": "保留中の添付ファイル削除を取り消しますか?",
28849
29242
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "この端末では CloudKit が制限されています。スクリーンタイム、MDM、iCloud の制限を確認してから、もう一度お試しください。",
28850
29243
  "settings.syncMobile.connectedToDropbox": "Dropbox に接続しました。",
@@ -28932,6 +29325,9 @@ App Store に更新があります。アプリのページを開きますか?`
28932
29325
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV のエンドポイントに接続できました。",
28933
29326
  "settings.persistentCaptureLabel": "通知バーからクイックキャプチャ",
28934
29327
  "settings.persistentCaptureDesc": "常駐する通知を表示して、ロック画面を含めどこからでも書き留められるようにします。",
29328
+ "settings.exactAlarmsLabel": "リマインダーが遅れる可能性があります",
29329
+ "settings.exactAlarmsDesc": "Android が Mindwtr に正確なアラームを許可していないため、リマインダーが最大 1 分遅れることがあります。",
29330
+ "settings.exactAlarmsAllow": "許可",
28935
29331
  "settings.appSearchLabel": "システム検索に表示する",
28936
29332
  "settings.appSearchDesc": "Android のシステム検索から、進行中のタスク・プロジェクト・エリアをタイトルで探せるようにします。データが端末の外に出ることはありません。",
28937
29333
  "captureNotification.title": "クイックキャプチャ",
@@ -29266,6 +29662,8 @@ App Store に更新があります。アプリのページを開きますか?`
29266
29662
  "settings.gettingStartedContentContinueDesc": "ここでの作業が終わったあとでも、ガイド付きの「はじめかた」プロジェクトとサンプルのインボックス項目を追加できます。",
29267
29663
  "settings.syncSetupGuideTitle": "データと同期の設定ガイド",
29268
29664
  "settings.syncSetupGuideDesc": "Dropbox・iCloud・WebDAV・フォルダ同期・復旧についての手引き。",
29665
+ "settings.syncEncryptionGuideTitle": "同期の暗号化ガイド",
29666
+ "settings.syncEncryptionGuideDesc": "何を保護するか、対応するサーバー、パスフレーズの仕組み。",
29269
29667
  "settings.importSetupGuideTitle": "読み込みの設定ガイド",
29270
29668
  "settings.importSetupGuideDesc": "Todoist・TickTick・DGT GTD・OmniFocus・Mindwtr CSV・Apple リマインダー・バックアップからの読み込み方法。",
29271
29669
  "settings.backupDiagnostics.newerVersion": "このバックアップは、より新しいバージョンの Mindwtr({{version}})で作成されています。",
@@ -29288,7 +29686,7 @@ App Store に更新があります。アプリのページを開きますか?`
29288
29686
  "settings.importDiagnostics.unmappedDate": "{{count}}件の日付は対応づけられなかったため、取り込みませんでした。",
29289
29687
  "settings.importDiagnostics.unmappedStatus": "{{count}}件のステータスは対応づけられなかったため、安全な既定値を使いました。",
29290
29688
  "settings.importDiagnostics.unsupportedRecurrence": "対応していない繰り返しルール{{count}}件は、メモとして残しました。",
29291
- "settings.syncRemoteBusy": "別の互換性のある Mindwtr デバイスがこの同期先を更新しています。完了するまで待ってから、もう一度同期してください。",
29689
+ "settings.syncRemoteBusy": "別の Mindwtr デバイスがこの同期先を一時的に確保しています。同期は自動的に再試行されます。",
29292
29690
  "settings.syncRemoteCleanupDeferred": "同期処理は完了しました。Mindwtr は一時的な同期ロックを削除できませんでしたが、ロックは自動的に期限切れになります。再試行は不要です。",
29293
29691
  "settings.syncAttachmentWriteDeferred": "一部の添付ファイルの変更を完了できませんでした。不足しているローカルファイルを復元するか、該当する添付ファイルを削除してから、もう一度同期してください。",
29294
29692
  "settings.syncFileAttachmentTooLarge": "Mindwtr はローカルの添付ファイルを保持しました。File Sync で同期できる添付ファイルは 100 MB 未満です。小さいファイルに置き換えるか添付ファイルを削除してから、もう一度同期してください。",
@@ -37046,6 +37444,8 @@ var init_ko = __esm(() => {
37046
37444
  "attachments.fileTooLarge": "파일이 너무 커서 업로드할 수 없습니다.",
37047
37445
  "attachments.fileNotReadable": "이 파일을 읽을 수 없어 첨부되지 않았습니다. 파일을 다른 폴더로 옮긴 뒤 다시 시도하세요.",
37048
37446
  "attachments.linkToFile": "파일에 연결…",
37447
+ "attachments.linkedFileElsewhere": "이 링크는 다른 기기에 있는 파일을 가리킵니다: {{path}}. 해당 기기에서 열거나, 링크 대신 파일 자체를 첨부하세요.",
37448
+ "attachments.openLinkFailed": "이 링크를 열 수 없습니다.",
37049
37449
  "attachments.invalidFileType": "지원되지 않는 파일 형식입니다.",
37050
37450
  "attachments.invalidLink": "유효한 URL을 입력하세요.",
37051
37451
  "attachments.photoUnavailableTitle": "사진 선택 도구를 사용할 수 없습니다.",
@@ -38669,6 +39069,9 @@ App Store에 업데이트가 있습니다. 지금 앱 목록을 여시겠습니
38669
39069
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV 엔드포인트에 접근할 수 있습니다.",
38670
39070
  "settings.persistentCaptureLabel": "알림 표시줄에서 빠른 수집",
38671
39071
  "settings.persistentCaptureDesc": "잠금 화면을 포함해 어디서나 수집할 수 있는 고정 알림을 유지합니다.",
39072
+ "settings.exactAlarmsLabel": "알림이 늦게 도착할 수 있습니다",
39073
+ "settings.exactAlarmsDesc": "Android가 Mindwtr에 정확한 알람을 허용하지 않아 알림이 최대 1분까지 늦게 울릴 수 있습니다.",
39074
+ "settings.exactAlarmsAllow": "허용",
38672
39075
  "settings.appSearchLabel": "시스템 검색에 노출",
38673
39076
  "settings.appSearchDesc": "Android 시스템 검색이 제목으로 활성 작업, 프로젝트, 영역을 찾을 수 있게 합니다. 어떤 데이터도 이 기기를 벗어나지 않습니다.",
38674
39077
  "captureNotification.title": "빠른 수집",
@@ -42864,6 +43267,9 @@ var init_fa = __esm(() => {
42864
43267
  "nav.main": "اصلی",
42865
43268
  "nav.inbox": "صندوق ورودی",
42866
43269
  "nav.board": "نمای تابلو",
43270
+ "nav.timeline": "خط زمانی",
43271
+ "timeline.empty": "هنوز چیزی زمان‌بندی نشده است",
43272
+ "timeline.emptyHint": "کارهایی که تاریخ شروع یا تاریخ سررسید دارند اینجا به صورت نوار نمایش داده می‌شوند.",
42867
43273
  "nav.projects": "پروژه‌ها",
42868
43274
  "nav.contexts": "زمینه‌ها",
42869
43275
  "nav.next": "اقدامات بعدی",
@@ -42950,6 +43356,7 @@ var init_fa = __esm(() => {
42950
43356
  "keybindings.goReference": "رفتن به مرجع",
42951
43357
  "keybindings.goCalendar": "رفتن به تقویم",
42952
43358
  "keybindings.goBoard": "رفتن به تابلو",
43359
+ "keybindings.goTimeline": "رفتن به خط زمانی",
42953
43360
  "keybindings.goDone": "رفتن به انجام‌شده",
42954
43361
  "keybindings.goArchived": "رفتن به بایگانی‌شده",
42955
43362
  "keybindings.switchArea": "تعویض حوزه ۱ تا ۹",
@@ -43128,9 +43535,12 @@ var init_fa = __esm(() => {
43128
43535
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
43129
43536
  "attachments.obsidianLinkInputHint": "یک پیوند obsidian:// را بچسبانید یا از «عنوان | obsidian://...» استفاده کنید.",
43130
43537
  "attachments.fileNotSupported": "پیوست فایل فقط در برنامه دسکتاپ پشتیبانی می‌شود.",
43538
+ "attachments.webUnavailable": "این پیوست در نسخه وب در دسترس نیست.",
43131
43539
  "attachments.fileTooLarge": "حجم فایل برای بارگذاری زیاد است.",
43132
43540
  "attachments.fileNotReadable": "این فایل خوانده نشد، بنابراین پیوست نگردید. آن را به پوشه دیگری منتقل کرده و دوباره امتحان کنید.",
43133
43541
  "attachments.linkToFile": "پیوند به فایل…",
43542
+ "attachments.linkedFileElsewhere": "این پیوند به فایلی روی دستگاه دیگری اشاره می‌کند: {{path}}. آن را همان‌جا باز کنید یا به‌جای پیوند، خود فایل را پیوست کنید.",
43543
+ "attachments.openLinkFailed": "باز کردن این پیوند ممکن نبود.",
43134
43544
  "attachments.invalidFileType": "نوع فایل پشتیبانی نمی‌شود.",
43135
43545
  "attachments.invalidLink": "لطفاً یک آدرس معتبر وارد کنید.",
43136
43546
  "attachments.photoUnavailableTitle": "انتخاب‌گر عکس در دسترس نیست",
@@ -43497,7 +43907,7 @@ var init_fa = __esm(() => {
43497
43907
  "projects.sectionPlaceholder": "عنوان بخش",
43498
43908
  "projects.noSection": "بدون بخش",
43499
43909
  "projects.sectionEmpty": "بدون وظیفه",
43500
- "projects.deleteSectionConfirm": "آیا مطمئنید می‌خواهید این بخش را حذف کنید؟",
43910
+ "projects.deleteSectionConfirm": "این بخش حذف شود؟ کارهای داخل آن حذف نمی‌شوند و به «بدون بخش» منتقل می‌شوند.",
43501
43911
  "projects.areaFilter": "فیلتر حوزه",
43502
43912
  "projects.allAreas": "همه حوزه‌ها",
43503
43913
  "projects.noArea": "بدون حوزه",
@@ -43530,6 +43940,8 @@ var init_fa = __esm(() => {
43530
43940
  "projects.complete": "تکمیل",
43531
43941
  "projects.archive": "بایگانی",
43532
43942
  "projects.reactivate": "فعال‌سازی مجدد",
43943
+ "projects.archivedTaskInspectionHint": "برای بررسی این کار دو بار ضربه بزنید. برای ویرایش، پروژه را دوباره فعال کنید.",
43944
+ "projects.archivedReadOnlyHint": "پروژه بایگانی شده است. برای ویرایش این کار، آن را دوباره فعال کنید.",
43533
43945
  "projects.actionsLabel": "اقدامات",
43534
43946
  "projects.archiveHelp": "بایگانی یک پروژه، آن و کارهای باقی‌مانده‌اش را تکمیل می‌کند — هر زمان می‌توانید دوباره فعالش کنید.",
43535
43947
  "projects.completeConfirm": "این پروژه تکمیل‌شده علامت‌گذاری و همه کارهایش پایان یابند؟",
@@ -43835,6 +44247,7 @@ var init_fa = __esm(() => {
43835
44247
  "settings.material3ThemeDesc": "استفاده از رنگ‌های متریال ۳ در اندروید",
43836
44248
  "settings.selectLang": "زبان دلخواه خود را انتخاب کنید",
43837
44249
  "settings.languagePartlyTranslated": "ترجمهٔ ناقص",
44250
+ "settings.videoTutorials": "آموزش‌های ویدیویی",
43838
44251
  "settings.privacy": "حریم خصوصی",
43839
44252
  "settings.mobile.appLock": "قفل برنامه",
43840
44253
  "settings.mobile.appLockDesc": "هنگام باز کردن Mindwtr یا بازگشت به برنامه، قفل دستگاه را الزامی کنید. این فقط نمای برنامه را محافظت می‌کند، نه پایگاه‌داده روی دستگاه.",
@@ -43922,6 +44335,8 @@ var init_fa = __esm(() => {
43922
44335
  "settings.syncEncryptionUnlock": "وارد کردن عبارت عبور",
43923
44336
  "settings.syncEncryptionDecline": "فعلاً نه",
43924
44337
  "settings.syncEncryptionPausedDesc": "تا زمانی که عبارت عبور را وارد نکنید، همگام‌سازی خودکار روی این دستگاه متوقف می‌ماند.",
44338
+ "settings.syncEncryptionLockedRecheckHint": "اگر این مکان همگام‌سازی دیگر فایل رمزگذاری‌شده ندارد، «همگام‌سازی اکنون» را بزنید؛ این دستگاه دوباره آن را بررسی می‌کند و ادامه می‌دهد.",
44339
+ "settings.syncEncryptionNoEncryptedRemote": "دیگر هیچ فایل رمزگذاری‌شده‌ای در این مکان همگام‌سازی وجود ندارد، بنابراین رمزگذاری روی این دستگاه اکنون خاموش است. برای رمزگذاری این مکان دوباره آن را روشن کنید.",
43925
44340
  "settings.syncEncryptionRemoteEncrypted": "این محل همگام‌سازی رمزگذاری شده است. برای ادامه همگام‌سازی، عبارت عبور آن را وارد کنید.",
43926
44341
  "settings.syncEncryptionRemotePlaintext": "همگام‌سازی متوقف شد: این محل همگام‌سازی دیگر رمزگذاری‌شده نیست. رمزگذاری همگام‌سازی را در این دستگاه خاموش کنید یا دوباره در محل همگام‌سازی روشن کنید.",
43927
44342
  "settings.syncEncryptionRemotePlaintextDesc": "دستگاه دیگری رمزگذاری را در این محل همگام‌سازی خاموش کرده است. هیچ چیزی در اینجا تغییر نکرده و از رمزگذاری خارج نشده است. برای ادامهٔ همگام‌سازی به شکل ساده، رمزگذاری همگام‌سازی را در این دستگاه خاموش کنید، یا رمزگذاری را دوباره در محل همگام‌سازی روشن کنید.",
@@ -44167,6 +44582,10 @@ var init_fa = __esm(() => {
44167
44582
  "settings.featureTimeEstimatesDesc": "افزودن برآورد سریع مدت زمان برای بلوک‌بندی زمانی.",
44168
44583
  "settings.featurePomodoro": "تایمر پومودورو",
44169
44584
  "settings.featurePomodoroDesc": "فعال‌سازی پنل اختیاری پومودورو در نمای تمرکز.",
44585
+ "settings.featureTimeline": "نمای خط زمانی",
44586
+ "settings.featureTimelineDesc": "نمایش خط زمانی فقط‌خواندنی کارهای تاریخ‌دار در نوار کناری.",
44587
+ "settings.sidebarViews": "نماهای نوار کناری",
44588
+ "settings.sidebarViewsDesc": "انتخاب کنید کدام نماها در نوار کناری نمایش داده شوند. نماهای پنهان از طریق جستجو در دسترس می‌مانند.",
44170
44589
  "settings.pomodoroCustomPreset": "پیش‌تنظیم سفارشی",
44171
44590
  "settings.pomodoroCustomPresetDesc": "یک پیش‌تنظیم اضافی تمرکز/استراحت اضافه کنید. تطبیق با یک پیش‌تنظیم داخلی فقط تراشه‌های داخلی را نگه می‌دارد.",
44172
44591
  "settings.pomodoroFocusMinutes": "دقایق تمرکز",
@@ -44253,6 +44672,7 @@ var init_fa = __esm(() => {
44253
44672
  "tags.title": "برچسب‌ها",
44254
44673
  "areas.edit": "ویرایش حوزه",
44255
44674
  "common.edit": "ویرایش",
44675
+ "common.view": "مشاهده",
44256
44676
  "common.add": "افزودن",
44257
44677
  "common.open": "باز کردن",
44258
44678
  "common.ok": "تأیید",
@@ -44320,6 +44740,7 @@ var init_fa = __esm(() => {
44320
44740
  "bulk.keepStatus": "نگه‌داشتن وضعیت",
44321
44741
  "bulk.keepProject": "نگه‌داشتن پروژه",
44322
44742
  "bulk.keepArea": "نگه‌داشتن حوزه",
44743
+ "bulk.keepSection": "نگه‌داشتن بخش",
44323
44744
  "bulk.waitingPersonRequired": "انتخاب کنید این موارد منتظر چه کسی هستند.",
44324
44745
  "bulk.deleting": "در حال حذف کارهای انتخاب‌شده...",
44325
44746
  "sort.label": "مرتب‌سازی",
@@ -44865,6 +45286,12 @@ var init_fa = __esm(() => {
44865
45286
  "settings.syncMobile.accountStatus": "وضعیت حساب",
44866
45287
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "دقیقاً همین آدرس بازگشت را در تنظیمات OAuth Dropbox اضافه کنید.",
44867
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": "خاموش",
44868
45295
  "settings.syncMobile.clearPendingAttachmentDeletes": "حذف‌های در انتظار پیوست پاک شوند؟",
44869
45296
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "CloudKit روی این دستگاه محدود شده است. Screen Time، MDM یا محدودیت‌های iCloud را بررسی کرده و دوباره امتحان کنید.",
44870
45297
  "settings.syncMobile.connectedToDropbox": "به Dropbox متصل شد.",
@@ -44952,6 +45379,9 @@ var init_fa = __esm(() => {
44952
45379
  "settings.syncMobile.webdavEndpointIsReachable": "نقطه پایانی WebDAV در دسترس است.",
44953
45380
  "settings.persistentCaptureLabel": "ثبت سریع در نوار اعلان",
44954
45381
  "settings.persistentCaptureDesc": "یک اعلان پایدار برای ثبت از هر جا، از جمله صفحه قفل، نگه دار.",
45382
+ "settings.exactAlarmsLabel": "یادآورها ممکن است دیر برسند",
45383
+ "settings.exactAlarmsDesc": "اندروید به Mindwtr اجازه‌ی تنظیم زنگ دقیق نمی‌دهد، بنابراین یادآورها ممکن است تا یک دقیقه دیرتر اجرا شوند.",
45384
+ "settings.exactAlarmsAllow": "اجازه بده",
44955
45385
  "settings.appSearchLabel": "نمایش در جستجوی سیستم",
44956
45386
  "settings.appSearchDesc": "به جستجوی سیستم اندروید اجازه بده کارها، پروژه‌ها و حوزه‌های فعال شما را بر اساس عنوان پیدا کند. هیچ داده‌ای از این دستگاه خارج نمی‌شود.",
44957
45387
  "captureNotification.title": "ثبت سریع",
@@ -45286,6 +45716,8 @@ var init_fa = __esm(() => {
45286
45716
  "settings.gettingStartedContentContinueDesc": "پس از پایان کار در اینجا نیز می‌توانید پروژه راهنمای «شروع کار» و موارد نمونه صندوق ورودی را اضافه کنید.",
45287
45717
  "settings.syncSetupGuideTitle": "راهنمای راه‌اندازی داده و همگام‌سازی",
45288
45718
  "settings.syncSetupGuideDesc": "نکات راه‌اندازی برای Dropbox، iCloud، WebDAV، همگام‌سازی فایل و بازیابی.",
45719
+ "settings.syncEncryptionGuideTitle": "راهنمای رمزگذاری همگام‌سازی",
45720
+ "settings.syncEncryptionGuideDesc": "چه چیزی را محافظت می‌کند، کدام سرورها از آن پشتیبانی می‌کنند و عبارت عبور چگونه کار می‌کند.",
45289
45721
  "settings.importSetupGuideTitle": "راهنمای راه‌اندازی واردکردن",
45290
45722
  "settings.importSetupGuideDesc": "روش‌های واردکردن پشتیبانی‌شده برای Todoist، TickTick، DGT GTD، OmniFocus، Mindwtr CSV، یادآورهای Apple و نسخه‌های پشتیبان.",
45291
45723
  "settings.backupDiagnostics.newerVersion": "این نسخه پشتیبان با نسخه جدیدتری از Mindwtr ({{version}}) ساخته شده است.",
@@ -45308,7 +45740,7 @@ var init_fa = __esm(() => {
45308
45740
  "settings.importDiagnostics.unmappedDate": "{{count}} مقدار تاریخ قابل تبدیل نبود و حذف شد.",
45309
45741
  "settings.importDiagnostics.unmappedStatus": "{{count}} مقدار وضعیت قابل تبدیل نبود و مقدار پیش‌فرض امن استفاده شد.",
45310
45742
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} قانون تکرار پشتیبانی‌نشده به‌صورت یادداشت نگه داشته شد.",
45311
- "settings.syncRemoteBusy": "یک دستگاه سازگار دیگرِ Mindwtr در حال به‌روزرسانی این مکان همگام‌سازی است. صبر کنید تا کارش تمام شود، سپس دوباره همگام‌سازی کنید.",
45743
+ "settings.syncRemoteBusy": "دستگاه Mindwtr دیگری این مکان همگام‌سازی را برای لحظه‌ای در اختیار دارد. همگام‌سازی به‌طور خودکار دوباره تلاش می‌کند.",
45312
45744
  "settings.syncRemoteCleanupDeferred": "عملیات همگام‌سازی کامل شد. Mindwtr نتوانست قفل موقت همگام‌سازی را حذف کند، اما این قفل خودکار منقضی می‌شود. نیازی به تلاش دوباره نیست.",
45313
45745
  "settings.syncAttachmentWriteDeferred": "برخی تغییرات پیوست‌ها کامل نشد. فایل‌های محلی گم‌شده را بازیابی کنید یا پیوست‌های مربوط را حذف کنید، سپس دوباره همگام‌سازی کنید.",
45314
45746
  "settings.syncFileAttachmentTooLarge": "Mindwtr پیوست محلی را نگه داشت. File Sync فقط می‌تواند پیوست‌های کوچک‌تر از ۱۰۰ مگابایت را همگام‌سازی کند. آن را با فایل کوچک‌تری جایگزین کنید یا پیوست را حذف کنید، سپس دوباره همگام‌سازی کنید.",
@@ -45343,6 +45775,9 @@ var init_sv = __esm(() => {
45343
45775
  "nav.main": "Huvud",
45344
45776
  "nav.inbox": "Inkorg",
45345
45777
  "nav.board": "Tavelvy",
45778
+ "nav.timeline": "Tidslinje",
45779
+ "timeline.empty": "Inget schemalagt än",
45780
+ "timeline.emptyHint": "Uppgifter med start- eller förfallodatum visas här som staplar.",
45346
45781
  "nav.projects": "Projekt",
45347
45782
  "nav.contexts": "Kontexter",
45348
45783
  "nav.next": "Nästa steg",
@@ -45429,6 +45864,7 @@ var init_sv = __esm(() => {
45429
45864
  "keybindings.goReference": "Gå till Referens",
45430
45865
  "keybindings.goCalendar": "Gå till Kalender",
45431
45866
  "keybindings.goBoard": "Gå till Tavla",
45867
+ "keybindings.goTimeline": "Gå till Tidslinje",
45432
45868
  "keybindings.goDone": "Gå till Klart",
45433
45869
  "keybindings.goArchived": "Gå till Arkiverat",
45434
45870
  "keybindings.switchArea": "Byt till område 1–9",
@@ -45607,9 +46043,12 @@ var init_sv = __esm(() => {
45607
46043
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
45608
46044
  "attachments.obsidianLinkInputHint": 'Klistra in en obsidian://-anteckningslänk, eller använd "Titel | obsidian://...".',
45609
46045
  "attachments.fileNotSupported": "Filbilagor stöds endast i skrivbordsappen.",
46046
+ "attachments.webUnavailable": "Den här bilagan är inte tillgänglig i webbappen.",
45610
46047
  "attachments.fileTooLarge": "Filen är för stor för att laddas upp.",
45611
46048
  "attachments.fileNotReadable": "Kunde inte läsa filen, så den bifogades inte. Flytta den till en annan mapp och försök igen.",
45612
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.",
45613
46052
  "attachments.invalidFileType": "Filtypen stöds inte.",
45614
46053
  "attachments.invalidLink": "Ange en giltig URL.",
45615
46054
  "attachments.photoUnavailableTitle": "Bildväljaren är inte tillgänglig",
@@ -45976,7 +46415,7 @@ var init_sv = __esm(() => {
45976
46415
  "projects.sectionPlaceholder": "Sektionens titel",
45977
46416
  "projects.noSection": "Ingen sektion",
45978
46417
  "projects.sectionEmpty": "Inga uppgifter",
45979
- "projects.deleteSectionConfirm": "Är du säker att du vill ta bort den här sektionen?",
46418
+ "projects.deleteSectionConfirm": "Ta bort denna sektion? Uppgifterna i den tas inte bort utan flyttas till Ingen sektion.",
45980
46419
  "projects.areaFilter": "Områdesfilter",
45981
46420
  "projects.allAreas": "Alla områden",
45982
46421
  "projects.noArea": "Inget område",
@@ -46009,6 +46448,8 @@ var init_sv = __esm(() => {
46009
46448
  "projects.complete": "Slutför",
46010
46449
  "projects.archive": "Arkivera",
46011
46450
  "projects.reactivate": "Återaktivera",
46451
+ "projects.archivedTaskInspectionHint": "Dubbeltryck för att granska uppgiften. Återaktivera projektet för att redigera den.",
46452
+ "projects.archivedReadOnlyHint": "Arkiverat projekt. Återaktivera det för att redigera uppgiften.",
46012
46453
  "projects.actionsLabel": "Åtgärder",
46013
46454
  "projects.archiveHelp": "Att arkivera ett projekt slutför det och dess kvarvarande uppgifter — återaktivera det när som helst.",
46014
46455
  "projects.completeConfirm": "Markera det här projektet som slutfört och avsluta alla dess uppgifter?",
@@ -46314,6 +46755,7 @@ var init_sv = __esm(() => {
46314
46755
  "settings.material3ThemeDesc": "Använd Material 3-färgtoken på Android",
46315
46756
  "settings.selectLang": "Välj ditt önskade språk",
46316
46757
  "settings.languagePartlyTranslated": "Delvis översatt",
46758
+ "settings.videoTutorials": "Videohandledningar",
46317
46759
  "settings.privacy": "Integritet",
46318
46760
  "settings.mobile.appLock": "Applås",
46319
46761
  "settings.mobile.appLockDesc": "Kräv enhetens lås när du öppnar Mindwtr eller återgår till appen. Det här skyddar appvyn, inte databasen på enheten.",
@@ -46401,6 +46843,8 @@ var init_sv = __esm(() => {
46401
46843
  "settings.syncEncryptionUnlock": "Ange lösenfras",
46402
46844
  "settings.syncEncryptionDecline": "Inte nu",
46403
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.",
46404
46848
  "settings.syncEncryptionRemoteEncrypted": "Den här synkplatsen är krypterad. Ange synklösenfrasen för att fortsätta synka.",
46405
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.",
46406
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.",
@@ -46646,6 +47090,10 @@ var init_sv = __esm(() => {
46646
47090
  "settings.featureTimeEstimatesDesc": "Lägg till snabba tidsuppskattningar för tidsblockering.",
46647
47091
  "settings.featurePomodoro": "Pomodoro-timer",
46648
47092
  "settings.featurePomodoroDesc": "Aktivera den valfria Pomodoro-panelen i Fokus-vyn.",
47093
+ "settings.featureTimeline": "Tidslinjevy",
47094
+ "settings.featureTimelineDesc": "Visa en skrivskyddad tidslinje över daterade uppgifter i sidofältet.",
47095
+ "settings.sidebarViews": "Vyer i sidofältet",
47096
+ "settings.sidebarViewsDesc": "Välj vilka vyer som visas i sidofältet. Dolda vyer nås fortfarande via sök.",
46649
47097
  "settings.pomodoroCustomPreset": "Anpassad förinställning",
46650
47098
  "settings.pomodoroCustomPresetDesc": "Lägg till en extra fokus-/pausförinställning. Matchar den en inbyggd förinställning behålls bara de inbyggda alternativen.",
46651
47099
  "settings.pomodoroFocusMinutes": "Fokusminuter",
@@ -46732,6 +47180,7 @@ var init_sv = __esm(() => {
46732
47180
  "tags.title": "Taggar",
46733
47181
  "areas.edit": "Redigera område",
46734
47182
  "common.edit": "Redigera",
47183
+ "common.view": "Visa",
46735
47184
  "common.add": "Lägg till",
46736
47185
  "common.open": "Öppna",
46737
47186
  "common.ok": "OK",
@@ -46799,6 +47248,7 @@ var init_sv = __esm(() => {
46799
47248
  "bulk.keepStatus": "Behåll status",
46800
47249
  "bulk.keepProject": "Behåll projekt",
46801
47250
  "bulk.keepArea": "Behåll område",
47251
+ "bulk.keepSection": "Behåll sektion",
46802
47252
  "bulk.waitingPersonRequired": "Välj vem de här posterna väntar på.",
46803
47253
  "bulk.deleting": "Tar bort markerade uppgifter...",
46804
47254
  "sort.label": "Sortera",
@@ -47344,6 +47794,12 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47344
47794
  "settings.syncMobile.accountStatus": "Kontostatus",
47345
47795
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Lägg till exakt den här omdirigerings-URI:n i Dropboxs OAuth-inställningar.",
47346
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",
47347
47803
  "settings.syncMobile.clearPendingAttachmentDeletes": "Rensa väntande bilageborttagningar?",
47348
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.",
47349
47805
  "settings.syncMobile.connectedToDropbox": "Ansluten till Dropbox.",
@@ -47431,6 +47887,9 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47431
47887
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV-slutpunkten är nåbar.",
47432
47888
  "settings.persistentCaptureLabel": "Snabbinspelning i aviseringsfältet",
47433
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",
47434
47893
  "settings.appSearchLabel": "Exponera för systemsökning",
47435
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.",
47436
47895
  "captureNotification.title": "Snabbinspelning",
@@ -47765,6 +48224,8 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47765
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.",
47766
48225
  "settings.syncSetupGuideTitle": "Guide för data- och synkroniseringsinställning",
47767
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.",
47768
48229
  "settings.importSetupGuideTitle": "Guide för importinställning",
47769
48230
  "settings.importSetupGuideDesc": "Importvägar som stöds för Todoist, TickTick, DGT GTD, OmniFocus, Mindwtr CSV, Apple Påminnelser och säkerhetskopior.",
47770
48231
  "settings.backupDiagnostics.newerVersion": "Den här säkerhetskopian skapades med en nyare version av Mindwtr ({{version}}).",
@@ -47787,7 +48248,7 @@ En uppdatering finns i App Store. Öppna appsidan nu?`,
47787
48248
  "settings.importDiagnostics.unmappedDate": "{{count}} datumvärden kunde inte mappas och utelämnades.",
47788
48249
  "settings.importDiagnostics.unmappedStatus": "{{count}} statusvärden kunde inte mappas; ett säkert standardvärde användes.",
47789
48250
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} upprepningsregler som inte stöds sparades som anteckningar.",
47790
- "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.",
47791
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.",
47792
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.",
47793
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.",
@@ -47820,7 +48281,7 @@ var init_i18n_locales = __esm(() => {
47820
48281
  mode: "overrides",
47821
48282
  native: "Tiếng Việt",
47822
48283
  nonLatin: false,
47823
- translatedKeyFloor: 2213
48284
+ translatedKeyFloor: 2280
47824
48285
  },
47825
48286
  zh: {
47826
48287
  loadSync: () => (init_zh_Hans(), __toCommonJS(exports_zh_Hans)),
@@ -47950,7 +48411,7 @@ var init_i18n_locales = __esm(() => {
47950
48411
  mode: "overrides",
47951
48412
  native: "한국어",
47952
48413
  nonLatin: true,
47953
- translatedKeyFloor: 2235
48414
+ translatedKeyFloor: 2240
47954
48415
  },
47955
48416
  it: {
47956
48417
  loadSync: () => (init_it(), __toCommonJS(exports_it)),
@@ -48211,12 +48672,15 @@ var init_settings_options = __esm(() => {
48211
48672
  GTD_SYNCED_FEATURE_FIELD_KEYS = [
48212
48673
  "priorities",
48213
48674
  "timeEstimates",
48214
- "pomodoro"
48675
+ "pomodoro",
48676
+ "timeline"
48215
48677
  ];
48216
48678
  });
48217
48679
 
48218
48680
  // ../../packages/core/src/async-utils.ts
48219
- 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) => {
48220
48684
  try {
48221
48685
  return decodeURIComponent(value);
48222
48686
  } catch {
@@ -48282,7 +48746,8 @@ function resolveFeatureFlags(settings) {
48282
48746
  return {
48283
48747
  priorities: settings?.features?.priorities !== false,
48284
48748
  timeEstimates: settings?.features?.timeEstimates !== false,
48285
- pomodoro: settings?.features?.pomodoro === true
48749
+ pomodoro: settings?.features?.pomodoro === true,
48750
+ timeline: settings?.features?.timeline === true
48286
48751
  };
48287
48752
  }
48288
48753
 
@@ -48311,6 +48776,15 @@ function buildBulkOrganizeTaskUpdate(task, input) {
48311
48776
  updates.projectId = undefined;
48312
48777
  }
48313
48778
  }
48779
+ if (hasOwn(input, "sectionId")) {
48780
+ const sectionId = normalizedOptionalString(input.sectionId);
48781
+ const effectiveProjectId = hasOwn(updates, "projectId") ? updates.projectId : normalizedOptionalString(task.projectId);
48782
+ if (!sectionId) {
48783
+ updates.sectionId = undefined;
48784
+ } else if (effectiveProjectId && effectiveProjectId === normalizedOptionalString(input.sectionProjectId)) {
48785
+ updates.sectionId = sectionId;
48786
+ }
48787
+ }
48314
48788
  const contexts = mergeTokens(task.contexts, input.contexts);
48315
48789
  if (contexts)
48316
48790
  updates.contexts = contexts;
@@ -67342,11 +67816,14 @@ var DEFAULT_TIMEOUT_MS = 30000, SYNC_LOCAL_INSECURE_URL_OPTIONS, isAbortError =
67342
67816
  onProgress(offset, total);
67343
67817
  }
67344
67818
  });
67345
- }, 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) => {
67346
67820
  const abortController = typeof AbortController === "function" ? new AbortController : null;
67347
67821
  let didTimeout = false;
67822
+ let firedAfterSuspension = false;
67823
+ const startedAt = Date.now();
67348
67824
  const timeoutId = abortController ? setTimeout(() => {
67349
67825
  didTimeout = true;
67826
+ firedAfterSuspension = Date.now() - startedAt > timeoutMs * SUSPENDED_REQUEST_FACTOR;
67350
67827
  abortController.abort(createAbortError(timeoutMessage));
67351
67828
  }, timeoutMs) : null;
67352
67829
  const signal = abortController?.signal ?? init.signal ?? undefined;
@@ -67381,7 +67858,7 @@ var DEFAULT_TIMEOUT_MS = 30000, SYNC_LOCAL_INSECURE_URL_OPTIONS, isAbortError =
67381
67858
  } catch (error2) {
67382
67859
  if (isAbortError(error2)) {
67383
67860
  if (didTimeout) {
67384
- throw new Error(timeoutMessage);
67861
+ throw new Error(firedAfterSuspension ? `${timeoutMessage}; ${SUSPENDED_REQUEST_MESSAGE}` : timeoutMessage);
67385
67862
  }
67386
67863
  if (externalSignal?.aborted) {
67387
67864
  throw getAbortSignalReason(externalSignal, "Request cancelled");
@@ -70549,6 +71026,28 @@ function buildRRuleString(rule, byDay, interval3, options = {}) {
70549
71026
  }
70550
71027
  return parts.join(";");
70551
71028
  }
71029
+ function editRRuleString(existingRRule, rule, overrides = {}) {
71030
+ const parsed = parseRRuleString(existingRRule);
71031
+ const byDay = hasRRuleEditOverride(overrides, "byDay") ? overrides.byDay : parsed.byDay;
71032
+ const interval3 = hasRRuleEditOverride(overrides, "interval") ? overrides.interval : parsed.interval;
71033
+ const byMonthDay = hasRRuleEditOverride(overrides, "byMonthDay") ? overrides.byMonthDay : parsed.byMonthDay;
71034
+ const count = hasRRuleEditOverride(overrides, "count") ? overrides.count : parsed.count;
71035
+ const weekStart = hasRRuleEditOverride(overrides, "weekStart") ? overrides.weekStart : parsed.weekStart;
71036
+ const until = hasRRuleEditOverride(overrides, "until") ? overrides.until : parsed.until;
71037
+ const edited = buildRRuleString(rule, byDay, interval3, {
71038
+ byMonthDay,
71039
+ count,
71040
+ weekStart,
71041
+ until
71042
+ });
71043
+ const opaqueTokens = existingRRule.split(";").map((part) => part.trim()).filter((part) => {
71044
+ const separator = part.indexOf("=");
71045
+ if (separator <= 0 || separator === part.length - 1)
71046
+ return false;
71047
+ return !EDITABLE_RRULE_TOKEN_KEYS.has(part.slice(0, separator).trim().toUpperCase());
71048
+ });
71049
+ return opaqueTokens.length > 0 ? `${edited};${opaqueTokens.join(";")}` : edited;
71050
+ }
70552
71051
  function getRecurrenceRRuleValue(value) {
70553
71052
  if (!value || typeof value === "string" || !value.rule)
70554
71053
  return "";
@@ -70557,16 +71056,25 @@ function getRecurrenceRRuleValue(value) {
70557
71056
  const count = getRecurrenceCountValue(value);
70558
71057
  const until = getRecurrenceUntilValue(value);
70559
71058
  if (value.byDay?.length) {
70560
- return buildRRuleString(value.rule, value.byDay, undefined, { count, until });
71059
+ return buildRRuleString(value.rule, value.byDay, undefined, {
71060
+ count,
71061
+ weekStart: value.weekStart,
71062
+ until
71063
+ });
70561
71064
  }
70562
71065
  if (value.byMonthDay?.length) {
70563
71066
  return buildRRuleString(value.rule, undefined, undefined, {
70564
71067
  byMonthDay: value.byMonthDay,
70565
71068
  count,
71069
+ weekStart: value.weekStart,
70566
71070
  until
70567
71071
  });
70568
71072
  }
70569
- return buildRRuleString(value.rule, undefined, undefined, { count, until });
71073
+ return buildRRuleString(value.rule, undefined, undefined, {
71074
+ count,
71075
+ weekStart: value.weekStart,
71076
+ until
71077
+ });
70570
71078
  }
70571
71079
  function hasRecurrenceRule(value) {
70572
71080
  return getRecurrenceRule(value) !== null;
@@ -71444,7 +71952,7 @@ var RECURRENCE_RULES, RECURRENCE_INTERVAL_MAX = 999, RRULE_SERIES_ID_KEY = "X-MI
71444
71952
  return;
71445
71953
  const weekly = normalized.filter((day) => WEEKDAY_ORDER.includes(day));
71446
71954
  return weekly.length > 0 ? Array.from(new Set(weekly)) : undefined;
71447
- }, getDateDay = (value) => {
71955
+ }, EDITABLE_RRULE_TOKEN_KEYS, hasRRuleEditOverride = (overrides, key) => Object.prototype.hasOwnProperty.call(overrides, key), getDateDay = (value) => {
71448
71956
  const parsed = safeParseDate(value);
71449
71957
  return parsed ? parsed.getDate() : undefined;
71450
71958
  }, getRecurrenceScheduleAnchorField = (dates) => ["dueDate", "startTime", "reviewAt"].find((field) => Boolean(dates[field])) ?? null, weekdayIndex = (weekday) => WEEKDAY_ORDER.indexOf(weekday), getLastDayOfMonth = (year, month) => {
@@ -71534,6 +72042,15 @@ var init_recurrence = __esm(() => {
71534
72042
  MONTHLY: "monthly",
71535
72043
  YEARLY: "yearly"
71536
72044
  };
72045
+ EDITABLE_RRULE_TOKEN_KEYS = new Set([
72046
+ "FREQ",
72047
+ "INTERVAL",
72048
+ "BYDAY",
72049
+ "BYMONTHDAY",
72050
+ "COUNT",
72051
+ "WKST",
72052
+ "UNTIL"
72053
+ ]);
71537
72054
  });
71538
72055
 
71539
72056
  // ../../packages/core/src/task-date-coherence.ts
@@ -71584,6 +72101,9 @@ var init_en = __esm(() => {
71584
72101
  "nav.main": "Main",
71585
72102
  "nav.inbox": "Inbox",
71586
72103
  "nav.board": "Board View",
72104
+ "nav.timeline": "Timeline",
72105
+ "timeline.empty": "Nothing scheduled yet",
72106
+ "timeline.emptyHint": "Tasks with a start or due date appear here as bars.",
71587
72107
  "nav.projects": "Projects",
71588
72108
  "nav.contexts": "Contexts",
71589
72109
  "nav.next": "Next Actions",
@@ -71670,6 +72190,7 @@ var init_en = __esm(() => {
71670
72190
  "keybindings.goReference": "Go to Reference",
71671
72191
  "keybindings.goCalendar": "Go to Calendar",
71672
72192
  "keybindings.goBoard": "Go to Board",
72193
+ "keybindings.goTimeline": "Go to Timeline",
71673
72194
  "keybindings.goDone": "Go to Done",
71674
72195
  "keybindings.goArchived": "Go to Archived",
71675
72196
  "keybindings.switchArea": "Switch to Area 1-9",
@@ -71847,9 +72368,12 @@ var init_en = __esm(() => {
71847
72368
  "attachments.obsidianLinkPlaceholder": "obsidian://open?vault=Vault&file=Note",
71848
72369
  "attachments.obsidianLinkInputHint": 'Paste an obsidian:// note link, or use "Title | obsidian://...".',
71849
72370
  "attachments.fileNotSupported": "File attachments are only supported in the desktop app.",
72371
+ "attachments.webUnavailable": "This attachment is not available in the web app.",
71850
72372
  "attachments.fileTooLarge": "File is too large to upload.",
71851
72373
  "attachments.fileNotReadable": "Couldn't read this file, so it was not attached. Move it to a different folder and try again.",
71852
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.",
71853
72377
  "attachments.invalidFileType": "Unsupported file type.",
71854
72378
  "attachments.invalidLink": "Please enter a valid URL.",
71855
72379
  "attachments.photoUnavailableTitle": "Photo picker unavailable",
@@ -72217,7 +72741,7 @@ var init_en = __esm(() => {
72217
72741
  "projects.sectionPlaceholder": "Section title",
72218
72742
  "projects.noSection": "No Section",
72219
72743
  "projects.sectionEmpty": "No tasks",
72220
- "projects.deleteSectionConfirm": "Are you sure you want to delete this section?",
72744
+ "projects.deleteSectionConfirm": "Delete this section? Its tasks are not deleted; they move to No Section.",
72221
72745
  "projects.areaFilter": "Area filter",
72222
72746
  "projects.allAreas": "All areas",
72223
72747
  "projects.noArea": "No area",
@@ -72250,6 +72774,8 @@ var init_en = __esm(() => {
72250
72774
  "projects.complete": "Complete",
72251
72775
  "projects.archive": "Archive",
72252
72776
  "projects.reactivate": "Reactivate",
72777
+ "projects.archivedTaskInspectionHint": "Double-tap to inspect this task. Reactivate the project to edit it.",
72778
+ "projects.archivedReadOnlyHint": "Archived project. Reactivate it to edit this task.",
72253
72779
  "projects.actionsLabel": "Actions",
72254
72780
  "projects.archiveHelp": "Archiving a project completes it and its remaining tasks — reactivate it anytime.",
72255
72781
  "projects.completeConfirm": "Mark this project completed and finish all its tasks?",
@@ -72555,6 +73081,7 @@ var init_en = __esm(() => {
72555
73081
  "settings.material3ThemeDesc": "Use Material 3 color tokens on Android",
72556
73082
  "settings.selectLang": "Select your preferred language",
72557
73083
  "settings.languagePartlyTranslated": "Partly translated",
73084
+ "settings.videoTutorials": "Video tutorials",
72558
73085
  "settings.privacy": "Privacy",
72559
73086
  "settings.mobile.appLock": "App lock",
72560
73087
  "settings.mobile.appLockDesc": "Require your device lock when opening Mindwtr or returning to the app. This protects the app view, not the on-device database.",
@@ -72642,6 +73169,8 @@ var init_en = __esm(() => {
72642
73169
  "settings.syncEncryptionUnlock": "Enter passphrase",
72643
73170
  "settings.syncEncryptionDecline": "Not now",
72644
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.",
72645
73174
  "settings.syncEncryptionRemoteEncrypted": "This sync location is encrypted. Enter its sync passphrase to continue syncing.",
72646
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.",
72647
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.",
@@ -72887,6 +73416,10 @@ var init_en = __esm(() => {
72887
73416
  "settings.featureTimeEstimatesDesc": "Add quick duration estimates for time blocking.",
72888
73417
  "settings.featurePomodoro": "Pomodoro timer",
72889
73418
  "settings.featurePomodoroDesc": "Enable the optional Pomodoro panel in Focus view.",
73419
+ "settings.featureTimeline": "Timeline view",
73420
+ "settings.featureTimelineDesc": "Show a read-only timeline of dated tasks in the sidebar.",
73421
+ "settings.sidebarViews": "Sidebar views",
73422
+ "settings.sidebarViewsDesc": "Choose which views appear in the sidebar. Hidden views stay available from search.",
72890
73423
  "settings.pomodoroCustomPreset": "Custom preset",
72891
73424
  "settings.pomodoroCustomPresetDesc": "Add one extra focus/break preset. Matching a built-in preset keeps the built-in chips only.",
72892
73425
  "settings.pomodoroFocusMinutes": "Focus minutes",
@@ -72973,6 +73506,7 @@ var init_en = __esm(() => {
72973
73506
  "tags.title": "Tags",
72974
73507
  "areas.edit": "Edit area",
72975
73508
  "common.edit": "Edit",
73509
+ "common.view": "View",
72976
73510
  "common.add": "Add",
72977
73511
  "common.open": "Open",
72978
73512
  "common.ok": "OK",
@@ -73040,6 +73574,7 @@ var init_en = __esm(() => {
73040
73574
  "bulk.keepStatus": "Keep status",
73041
73575
  "bulk.keepProject": "Keep project",
73042
73576
  "bulk.keepArea": "Keep area",
73577
+ "bulk.keepSection": "Keep section",
73043
73578
  "bulk.waitingPersonRequired": "Choose who these items are waiting for.",
73044
73579
  "bulk.deleting": "Deleting selected tasks...",
73045
73580
  "sort.label": "Sort",
@@ -73585,6 +74120,12 @@ Update is available on the App Store. Open app listing now?`,
73585
74120
  "settings.syncMobile.accountStatus": "Account status",
73586
74121
  "settings.syncMobile.addThisExactRedirectUriInDropboxOauthSettings": "Add this exact redirect URI in Dropbox OAuth settings.",
73587
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",
73588
74129
  "settings.syncMobile.clearPendingAttachmentDeletes": "Clear pending attachment deletes?",
73589
74130
  "settings.syncMobile.cloudkitIsRestrictedOnThisDeviceCheckScreenTimeMdm": "CloudKit is restricted on this device. Check Screen Time, MDM, or iCloud restrictions, then try again.",
73590
74131
  "settings.syncMobile.connectedToDropbox": "Connected to Dropbox.",
@@ -73672,6 +74213,9 @@ Update is available on the App Store. Open app listing now?`,
73672
74213
  "settings.syncMobile.webdavEndpointIsReachable": "WebDAV endpoint is reachable.",
73673
74214
  "settings.persistentCaptureLabel": "Quick capture in notification bar",
73674
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",
73675
74219
  "settings.appSearchLabel": "Expose to system search",
73676
74220
  "settings.appSearchDesc": "Let Android system search find your active tasks, projects, and areas by title. Nothing leaves this device.",
73677
74221
  "captureNotification.title": "Quick capture",
@@ -74006,6 +74550,8 @@ Update is available on the App Store. Open app listing now?`,
74006
74550
  "settings.gettingStartedContentContinueDesc": "When you are done here, you can still add the guided Getting Started project and sample inbox items.",
74007
74551
  "settings.syncSetupGuideTitle": "Data & Sync setup guide",
74008
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.",
74009
74555
  "settings.importSetupGuideTitle": "Import setup guide",
74010
74556
  "settings.importSetupGuideDesc": "Supported Todoist, TickTick, DGT GTD, OmniFocus, Mindwtr CSV, Apple Reminders, and backup import paths.",
74011
74557
  "settings.backupDiagnostics.newerVersion": "This backup was created by a newer Mindwtr version ({{version}}).",
@@ -74028,7 +74574,7 @@ Update is available on the App Store. Open app listing now?`,
74028
74574
  "settings.importDiagnostics.unmappedDate": "{{count}} date value(s) could not be mapped and were omitted.",
74029
74575
  "settings.importDiagnostics.unmappedStatus": "{{count}} status value(s) could not be mapped and used a safe default.",
74030
74576
  "settings.importDiagnostics.unsupportedRecurrence": "{{count}} unsupported repeat rule(s) were kept as notes.",
74031
- "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.",
74032
74578
  "settings.syncRemoteCleanupDeferred": "The sync operation completed. Mindwtr could not remove the temporary sync lock, but it expires automatically. No retry is needed.",
74033
74579
  "settings.syncAttachmentWriteDeferred": "Some attachment changes could not finish. Restore any missing local files or remove the affected attachments, then sync again.",
74034
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.",
@@ -84536,7 +85082,8 @@ function buildQuickAddParseOptions(settings, source = {}) {
84536
85082
  knownPeople: getPersonOptionNames(source.people, tasks),
84537
85083
  defaultScheduleTime: normalizeClockTimeInput(settings?.gtd?.defaultScheduleTime) || undefined,
84538
85084
  preserveText: settings?.quickAddAutoClean !== true,
84539
- naturalLanguageDates: isNaturalLanguageDatesEnabled(settings)
85085
+ naturalLanguageDates: isNaturalLanguageDatesEnabled(settings),
85086
+ parsePriority: resolveFeatureFlags(settings).priorities
84540
85087
  };
84541
85088
  }
84542
85089
  function getQuickAddProjectInitialProps(props, fallbackAreaId) {
@@ -85040,7 +85587,7 @@ function parseQuickAdd(input, projects, now2 = new Date, areas, options = {}) {
85040
85587
  }
85041
85588
  }
85042
85589
  let priority;
85043
- const priorityMatch = working.match(/(?:^|\s)\/priority:([^\s/]+)/i);
85590
+ const priorityMatch = options.parsePriority === false ? null : working.match(/(?:^|\s)\/priority:([^\s/]+)/i);
85044
85591
  if (priorityMatch) {
85045
85592
  const token = restoreEscapes(priorityMatch[1] ?? "").trim().toLowerCase();
85046
85593
  priority = PRIORITY_TOKENS[token];
@@ -85213,7 +85760,12 @@ function parseQuickAdd(input, projects, now2 = new Date, areas, options = {}) {
85213
85760
  detectedDate
85214
85761
  };
85215
85762
  }
85216
- var STATUS_TOKENS, ENERGY_TOKENS, PRIORITY_TOKENS, ESCAPE_SENTINEL = "__MW_ESC__", QUICK_ADD_ESCAPE_CHARS, QUICK_ADD_FOCUS_COMMAND_PATTERN, QUICK_ADD_COMMAND_START, QUICK_ADD_COMMAND_BOUNDARY, QUICK_ADD_INLINE_CONTROL_BOUNDARY, SIMPLE_TASK_TOKEN_RE, RICH_TASK_TOKEN_RE, NATURAL_TIME_HINT_RE, PURE_TIME_ONLY_RE, BARE_MONTH_RE, TRAILING_DATE_SUFFIX_RE, TRAILING_DATE_SEPARATOR_RE, dotDateParser, quickAddChrono, quickAddChronoDayFirst, getQuickAddChrono = () => isActiveDateFormatDayFirst() ? quickAddChronoDayFirst : quickAddChrono, LOCALE_CHRONO_FACTORY, localeChronoCache, TRAILING_DATE_INTRO_WORDS, localizedMonthNamesCache, CONTAINS_LETTER_RE;
85763
+ function formatQuickAddHelp(help, flags) {
85764
+ if (flags.priorities)
85765
+ return help;
85766
+ return help.replace(QUICK_ADD_PRIORITY_HELP_TOKEN, "");
85767
+ }
85768
+ var STATUS_TOKENS, ENERGY_TOKENS, PRIORITY_TOKENS, ESCAPE_SENTINEL = "__MW_ESC__", QUICK_ADD_ESCAPE_CHARS, QUICK_ADD_FOCUS_COMMAND_PATTERN, QUICK_ADD_COMMAND_START, QUICK_ADD_COMMAND_BOUNDARY, QUICK_ADD_INLINE_CONTROL_BOUNDARY, SIMPLE_TASK_TOKEN_RE, RICH_TASK_TOKEN_RE, NATURAL_TIME_HINT_RE, PURE_TIME_ONLY_RE, BARE_MONTH_RE, TRAILING_DATE_SUFFIX_RE, TRAILING_DATE_SEPARATOR_RE, dotDateParser, quickAddChrono, quickAddChronoDayFirst, getQuickAddChrono = () => isActiveDateFormatDayFirst() ? quickAddChronoDayFirst : quickAddChrono, LOCALE_CHRONO_FACTORY, localeChronoCache, TRAILING_DATE_INTRO_WORDS, localizedMonthNamesCache, CONTAINS_LETTER_RE, QUICK_ADD_PRIORITY_HELP_TOKEN;
85217
85769
  var init_quick_add = __esm(() => {
85218
85770
  init_esm();
85219
85771
  init_date_fns();
@@ -85298,6 +85850,7 @@ var init_quick_add = __esm(() => {
85298
85850
  };
85299
85851
  localizedMonthNamesCache = new Map;
85300
85852
  CONTAINS_LETTER_RE = /\p{L}/u;
85853
+ QUICK_ADD_PRIORITY_HELP_TOKEN = /\/priority:<[^>]*>(?:\s*[,،、]\s*|\s+)?/u;
85301
85854
  });
85302
85855
 
85303
85856
  // ../../packages/core/src/project-utils.ts
@@ -85312,6 +85865,19 @@ function normalizeProjectTaskSortBy(value) {
85312
85865
  }
85313
85866
  return;
85314
85867
  }
85868
+ function getProjectSectionsForView(project, visibleSections, allSections = visibleSections) {
85869
+ if (!project)
85870
+ return [];
85871
+ const archivedHistory = project.status === "archived";
85872
+ const source = archivedHistory ? allSections : visibleSections;
85873
+ return source.filter((section) => {
85874
+ if (section.projectId !== project.id)
85875
+ return false;
85876
+ if (!section.deletedAt)
85877
+ return true;
85878
+ return archivedHistory && section.projectArchivedAt === section.deletedAt && section.deletedAtBeforeProjectArchive === null;
85879
+ }).sort(compareProjectSections);
85880
+ }
85315
85881
  function getSequentialProjectTaskCues(project, tasks, options = {}) {
85316
85882
  const cues = new Map;
85317
85883
  if (!project?.isSequential)
@@ -85443,7 +86009,13 @@ function getProjectChoiceState(browseProjects, query, searchProjects = browsePro
85443
86009
  canCreate: !exactMatch
85444
86010
  };
85445
86011
  }
85446
- var TASK_SORT_BY_VALUES, TASK_SORT_BY_VALUE_SET, PROJECT_TASK_SORT_BY_VALUES, getTaskProjectOrder = (task) => {
86012
+ var TASK_SORT_BY_VALUES, TASK_SORT_BY_VALUE_SET, PROJECT_TASK_SORT_BY_VALUES, compareProjectSections = (a, b) => {
86013
+ const aOrder = Number.isFinite(a.order) ? a.order : 0;
86014
+ const bOrder = Number.isFinite(b.order) ? b.order : 0;
86015
+ if (aOrder !== bOrder)
86016
+ return aOrder - bOrder;
86017
+ return a.title.localeCompare(b.title);
86018
+ }, getTaskProjectOrder = (task) => {
85447
86019
  if (Number.isFinite(task.order))
85448
86020
  return task.order;
85449
86021
  if (Number.isFinite(task.orderNum))
@@ -86368,6 +86940,36 @@ function buildTrashTimeline(tasks, projects) {
86368
86940
  ];
86369
86941
  return items.sort((left, right) => compareDeletedAtDesc(getTrashTimelineEntity(left), getTrashTimelineEntity(right)));
86370
86942
  }
86943
+ function resolveTaskSortByForFeatures(sortBy, settings) {
86944
+ const flags = resolveFeatureFlags(settings);
86945
+ if (sortBy === "timeEstimate" && !flags.timeEstimates)
86946
+ return "default";
86947
+ if (sortBy === "priority" && !flags.priorities)
86948
+ return "default";
86949
+ return sortBy;
86950
+ }
86951
+ function resolveTaskGroupByForFeatures(groupBy, settings) {
86952
+ if (groupBy === "priority" && !resolveFeatureFlags(settings).priorities)
86953
+ return "none";
86954
+ return groupBy;
86955
+ }
86956
+ function resolveTaskPerspectiveForFeatures({
86957
+ sortBy,
86958
+ groupBy,
86959
+ settings,
86960
+ hasActiveFilters,
86961
+ hasCurrentCriteria,
86962
+ activeSavedFilterId
86963
+ }) {
86964
+ const effectiveSortBy = resolveTaskSortByForFeatures(sortBy, settings);
86965
+ const effectiveGroupBy = resolveTaskGroupByForFeatures(groupBy, settings);
86966
+ return {
86967
+ effectiveSortBy,
86968
+ effectiveGroupBy,
86969
+ isDefaultPerspective: !hasActiveFilters && activeSavedFilterId === null && effectiveSortBy === "default",
86970
+ canSavePerspective: activeSavedFilterId === null && (hasCurrentCriteria || effectiveSortBy !== "default" || effectiveGroupBy !== "none")
86971
+ };
86972
+ }
86371
86973
  function sortTasksBy(tasks, sortBy = "default") {
86372
86974
  if (!sortBy || sortBy === "default") {
86373
86975
  return sortTasks(tasks);
@@ -86517,7 +87119,7 @@ function sortTasksBySavedPreference(tasks, sortBy, options = {}) {
86517
87119
  case "created-desc":
86518
87120
  return withFallbacks(byCreatedDesc);
86519
87121
  case "priority":
86520
- return withFallbacks(byPriority, byDue, byStart, byCreatedAsc);
87122
+ return options.prioritizeByPriority ? withFallbacks(byPriority, byDue, byStart, byCreatedAsc) : withFallbacks(byDue, byStart, byCreatedAsc);
86521
87123
  case "energy":
86522
87124
  return withFallbacks(byEnergy, byDue, byStart, byCreatedAsc);
86523
87125
  case "timeEstimate":
@@ -87518,6 +88120,7 @@ var init_sync_service_utils = __esm(() => {
87518
88120
  READONLY_ERROR_PATTERN = /isn't writable|not writable|read-only|read only|permission denied|EACCES/i;
87519
88121
  OFFLINE_ERROR_PATTERNS = [
87520
88122
  /offline state detected/i,
88123
+ /interrupted while the app was suspended/i,
87521
88124
  /network request failed/i,
87522
88125
  /internet connection appears to be offline/i,
87523
88126
  /airplane mode/i,
@@ -87658,7 +88261,18 @@ var init_tombstone_compaction = __esm(() => {
87658
88261
  function filterNotDeleted(items) {
87659
88262
  return items.filter((item) => !item.deletedAt);
87660
88263
  }
87661
- var MISSING_ATTACHMENT_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", normalizeWebdavUrl = (rawUrl) => {
88264
+ var MISSING_ATTACHMENT_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", MISSING_SETTINGS_SYNC_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", advanceLatestSyncTimestamp = (...values) => {
88265
+ let latestTime = Date.parse(MISSING_SETTINGS_SYNC_TIMESTAMP_SENTINEL);
88266
+ for (const value of values) {
88267
+ const parsed = Date.parse(value ?? "");
88268
+ if (Number.isFinite(parsed) && parsed > latestTime)
88269
+ latestTime = parsed;
88270
+ }
88271
+ if (!Number.isFinite(latestTime))
88272
+ return;
88273
+ const advanced = new Date(latestTime + 1);
88274
+ return Number.isFinite(advanced.getTime()) ? advanced.toISOString() : undefined;
88275
+ }, normalizeWebdavUrl = (rawUrl) => {
87662
88276
  const splitIndex = rawUrl.search(/[?#]/);
87663
88277
  const pathEnd = splitIndex >= 0 ? splitIndex : rawUrl.length;
87664
88278
  const path = rawUrl.slice(0, pathEnd).replace(/\/+$/, "");
@@ -87749,9 +88363,20 @@ var MISSING_ATTACHMENT_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", normaliz
87749
88363
  assertNoPendingUploadList(findPendingAttachmentUploads(data).filter((item) => item.reason === "content-replacement"));
87750
88364
  }, hasPendingSyncSideEffects = (data) => Boolean(data.settings.pendingRemoteWriteAt) || findPendingAttachmentUploads(data).length > 0 || Boolean(data.settings.attachments?.pendingRemoteDeletes?.length), sanitizeSettingsForRemote = (settings) => {
87751
88365
  const prefs = settings.syncPreferences ?? {};
88366
+ const remotePrefs = { ...prefs };
88367
+ let remotePrefsUpdatedAt = settings.syncPreferencesUpdatedAt ? { ...settings.syncPreferencesUpdatedAt } : undefined;
88368
+ if (isSettingsSyncGroupEnabled(prefs, "gtd")) {
88369
+ remotePrefs.gtd = true;
88370
+ if (prefs.gtd === undefined) {
88371
+ const materializedAt = advanceLatestSyncTimestamp(remotePrefsUpdatedAt?.preferences, remotePrefsUpdatedAt?.gtd);
88372
+ if (materializedAt) {
88373
+ remotePrefsUpdatedAt = { ...remotePrefsUpdatedAt, preferences: materializedAt };
88374
+ }
88375
+ }
88376
+ }
87752
88377
  const next = {
87753
- syncPreferences: { ...prefs },
87754
- syncPreferencesUpdatedAt: settings.syncPreferencesUpdatedAt ? { ...settings.syncPreferencesUpdatedAt } : undefined
88378
+ syncPreferences: remotePrefs,
88379
+ syncPreferencesUpdatedAt: remotePrefsUpdatedAt
87755
88380
  };
87756
88381
  if (prefs.appearance === true) {
87757
88382
  next.theme = settings.theme;
@@ -87870,6 +88495,56 @@ var MISSING_ATTACHMENT_TIMESTAMP_SENTINEL = "1970-01-01T00:00:00.000Z", normaliz
87870
88495
  delete comparable[key];
87871
88496
  }
87872
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 });
87873
88548
  }, appendEntityRevisions = (parts, entities) => {
87874
88549
  parts.push(String(entities?.length ?? 0));
87875
88550
  for (const entity of entities ?? []) {
@@ -88321,6 +88996,17 @@ var hasOwnField2 = (value, field) => Object.prototype.hasOwnProperty.call(value,
88321
88996
  sequentialWithinSectionProjectIds,
88322
88997
  focusedProjectCount
88323
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;
88324
89010
  }, computeTaskDerivedState = (tasks, tasksById) => {
88325
89011
  const resolvedTasksById = tasksById ?? new Map;
88326
89012
  const activeTasksByStatus = new Map;
@@ -88371,7 +89057,7 @@ var hasOwnField2 = (value, field) => Object.prototype.hasOwnProperty.call(value,
88371
89057
  if (dateCoherenceIssues.length > 0) {
88372
89058
  dateCoherenceIssuesByTaskId.set(task.id, dateCoherenceIssues);
88373
89059
  }
88374
- if (task.isFocusedToday && task.status !== "done" && task.status !== "reference" && task.status !== "archived") {
89060
+ if (isTaskCountedAsFocused(task)) {
88375
89061
  focusedCount += 1;
88376
89062
  focusedTasks.push(task);
88377
89063
  }
@@ -88723,6 +89409,34 @@ async function runAttachmentTransferLifecycle(options) {
88723
89409
  }
88724
89410
  }
88725
89411
  };
89412
+ const provePendingUploadIdentity = async (attachment, localPath) => {
89413
+ if (!options.createUploadSnapshot)
89414
+ return false;
89415
+ if (options.getLocalFileStat) {
89416
+ const candidateStat = await options.getLocalFileStat(localPath, attachment).catch(() => null);
89417
+ if (candidateStat)
89418
+ assertUploadStatAllowed(candidateStat);
89419
+ }
89420
+ const snapshot = await options.createUploadSnapshot(localPath, attachment);
89421
+ if (!snapshot)
89422
+ return false;
89423
+ try {
89424
+ const snapshotHash = snapshot.fileHash.trim().toLowerCase();
89425
+ if (!isSha256Hex(snapshotHash)) {
89426
+ options.onLocalEditRace?.(attachment);
89427
+ return false;
89428
+ }
89429
+ assertUploadStatAllowed(snapshot.stat);
89430
+ applyAttachmentContentStat(attachment, snapshot.stat, snapshotHash);
89431
+ return true;
89432
+ } finally {
89433
+ try {
89434
+ await snapshot.dispose();
89435
+ } catch (error2) {
89436
+ options.onUploadError(attachment, error2);
89437
+ }
89438
+ }
89439
+ };
88726
89440
  for (const original of options.attachmentsById.values()) {
88727
89441
  await options.beforeEachAttachment?.();
88728
89442
  if (original.kind !== "file")
@@ -88752,6 +89466,17 @@ async function runAttachmentTransferLifecycle(options) {
88752
89466
  itemMutated = true;
88753
89467
  }
88754
89468
  const mayReadForSync = existsLocally && (options.canUploadFrom?.(localPath, attachment) ?? true);
89469
+ if (hasPendingContentUpload && options.deferUploads && mayReadForSync && !isSha256Hex(attachment.fileHash)) {
89470
+ try {
89471
+ if (await provePendingUploadIdentity(attachment, localPath)) {
89472
+ itemMutated = true;
89473
+ }
89474
+ } catch (error2) {
89475
+ if (options.isFatalError?.(error2))
89476
+ throw error2;
89477
+ options.onUploadError(attachment, error2);
89478
+ }
89479
+ }
88755
89480
  if (hasPendingContentUpload && !options.deferUploads) {
88756
89481
  const expectedPendingHash = attachment.fileHash?.trim().toLowerCase();
88757
89482
  if (!isSha256Hex(expectedPendingHash)) {
@@ -89544,6 +90269,7 @@ var normalizeAppData = (data) => ({
89544
90269
  isFocused: normalizeSyncedBoolean(project.isFocused),
89545
90270
  attachments: normalizeAttachmentsForSyncMerge(project.attachments),
89546
90271
  dueDate: normalizeOptionalString2(project.dueDate),
90272
+ startDate: normalizeOptionalString2(project.startDate),
89547
90273
  reviewAt: normalizeOptionalString2(project.reviewAt),
89548
90274
  areaId: normalizeOptionalString2(project.areaId),
89549
90275
  areaTitle: normalizeOptionalString2(project.areaTitle)
@@ -91750,6 +92476,23 @@ function createEmptyEntityStats(localTotal, incomingTotal) {
91750
92476
  conflictSamples: []
91751
92477
  };
91752
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
+ }
91753
92496
  function mergeEntitiesWithStats(local, incoming, mergeConflict, normalizeForComparison, entityType = "entity", nowIso) {
91754
92497
  const localMap = new Map(local.map((item) => [item.id, item]));
91755
92498
  const incomingMap = new Map(incoming.map((item) => [item.id, item]));
@@ -92233,11 +92976,12 @@ function mergeAppDataWithStats(local, incoming, options = {}) {
92233
92976
  const localRev = localAttachment.contentRev ?? 0;
92234
92977
  const incomingRev = incomingAttachment.contentRev ?? 0;
92235
92978
  const contentSource = localRev === incomingRev ? winner : localRev > incomingRev ? localAttachment : incomingAttachment;
92236
- const fileHash = contentSource.fileHash || localAttachment.fileHash || incomingAttachment.fileHash;
92237
92979
  const contentRev = contentSource.contentRev;
92980
+ const localPendingCandidateWon = localAttachment.pendingContentUpload === true && contentSource === localAttachment;
92981
+ const fileHash = localPendingCandidateWon ? localAttachment.fileHash : contentSource.fileHash || localAttachment.fileHash || incomingAttachment.fileHash;
92238
92982
  const normalizedLocalFileHash = localAttachment.fileHash?.trim().toLowerCase();
92239
92983
  const normalizedResolvedFileHash = fileHash?.trim().toLowerCase();
92240
- const localIdentitySurvived = (contentRev ?? 0) === localRev && Boolean(normalizedLocalFileHash) && normalizedResolvedFileHash === normalizedLocalFileHash;
92984
+ const localIdentitySurvived = (contentRev ?? 0) === localRev && (Boolean(normalizedLocalFileHash) && normalizedResolvedFileHash === normalizedLocalFileHash || localPendingCandidateWon && !normalizedLocalFileHash && !normalizedResolvedFileHash);
92241
92985
  const localPendingIdentitySurvived = localAttachment.pendingContentUpload === true && localIdentitySurvived;
92242
92986
  const statSource = localIdentitySurvived ? localAttachment : contentSource;
92243
92987
  return {
@@ -92299,7 +93043,7 @@ function mergeAppDataWithStats(local, incoming, options = {}) {
92299
93043
  ...attachment,
92300
93044
  uri: safeUri ?? "",
92301
93045
  cloudKey: resolveCloudKey(attachment, localFile, incomingFile),
92302
- fileHash: attachment.deletedAt ? attachment.fileHash : attachment.fileHash || localFile?.fileHash || incomingFile?.fileHash,
93046
+ fileHash: attachment.deletedAt ? attachment.fileHash : attachment.pendingContentUpload === true ? attachment.fileHash : attachment.fileHash || localFile?.fileHash || incomingFile?.fileHash,
92303
93047
  localStatus: attachment.deletedAt ? attachment.localStatus : uriAvailable ? attachment.localStatus ?? "available" : normalizeMissingFileStatus(attachment.localStatus, attachment.deletedAt)
92304
93048
  };
92305
93049
  });
@@ -92403,7 +93147,8 @@ async function performSyncCycleUnlocked(io) {
92403
93147
  const remoteData = purgeExpiredTombstones(remoteDocument.data, nowIso, io.tombstoneRetentionDays).data;
92404
93148
  io.onStep?.("merge");
92405
93149
  await yieldToUi();
92406
- 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, {
92407
93152
  nowIso,
92408
93153
  preferIncomingAttachmentCloudKeys: io.preferIncomingAttachmentCloudKeys
92409
93154
  });
@@ -92491,6 +93236,34 @@ async function performSyncCycleUnlocked(io) {
92491
93236
  throw new Error(`Sync validation failed: ${sample}`);
92492
93237
  }
92493
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
+ }
92494
93267
  const finalDataWithPendingRemoteWrite = withPendingRemoteWriteFlag(finalData, pendingRemoteWriteMeta?.pendingAt ?? nowIso, pendingRemoteWriteMeta?.attempts);
92495
93268
  io.onStep?.("write-local");
92496
93269
  await yieldToUi();
@@ -94324,6 +95097,8 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94324
95097
  order: 0,
94325
95098
  tagIds: [],
94326
95099
  supportNotes: resolveStarterString(lang, STARTER_PROJECT_NOTES_KEY),
95100
+ isSequential: false,
95101
+ isFocused: false,
94327
95102
  rev: 1,
94328
95103
  ...revisionMeta,
94329
95104
  createdAt: nowIso,
@@ -94347,7 +95122,9 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94347
95122
  projectId,
94348
95123
  order: index,
94349
95124
  orderNum: index,
94350
- isFocusedToday: template.isFocusedToday,
95125
+ isFocusedToday: template.isFocusedToday ?? false,
95126
+ suppressMindwtrReminders: false,
95127
+ pushCount: 0,
94351
95128
  rev: 1,
94352
95129
  ...revisionMeta,
94353
95130
  createdAt: nowIso,
@@ -94360,6 +95137,9 @@ var normalizeStarterTaskTitle = (title) => title.trim().toLowerCase(), STARTER_T
94360
95137
  taskMode: "task",
94361
95138
  tags: [],
94362
95139
  contexts: [],
95140
+ isFocusedToday: false,
95141
+ suppressMindwtrReminders: false,
95142
+ pushCount: 0,
94363
95143
  rev: 1,
94364
95144
  ...revisionMeta,
94365
95145
  createdAt: nowIso,
@@ -94998,7 +95778,8 @@ var STORAGE_TIMEOUT_MS = 15000, SLOW_FETCH_LOG_THRESHOLD_MS = 1000, getFetchData
94998
95778
  },
94999
95779
  setHighlightTask: (id2) => {
95000
95780
  set5({ highlightTaskId: id2, highlightTaskAt: id2 ? Date.now() : null });
95001
- }
95781
+ },
95782
+ getFocusedCount: () => selectFocusedCount(get().tasks)
95002
95783
  });
95003
95784
  var init_store_settings = __esm(() => {
95004
95785
  init_logger();
@@ -95251,17 +96032,22 @@ var createAreaActions = ({
95251
96032
  const nextName = updates.name !== undefined ? updates.name.trim() : area.name;
95252
96033
  let projectsChanged = false;
95253
96034
  let newAllProjects = state._allProjects;
95254
- if ("color" in updates) {
95255
- 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()) {
95256
96039
  newAllProjects = state._allProjects.map((project) => {
95257
96040
  if (project.areaId !== id2)
95258
96041
  return project;
95259
- if (project.color === nextAreaColor)
96042
+ const wantsColor = repaintColor && project.color !== nextAreaColor;
96043
+ const wantsTitle = project.areaTitle !== nextAreaTitle;
96044
+ if (!wantsColor && !wantsTitle)
95260
96045
  return project;
95261
96046
  projectsChanged = true;
95262
96047
  return {
95263
96048
  ...project,
95264
- color: nextAreaColor,
96049
+ ...wantsColor ? { color: nextAreaColor } : {},
96050
+ ...wantsTitle ? { areaTitle: nextAreaTitle } : {},
95265
96051
  updatedAt: now3,
95266
96052
  rev: nextRevision(project.rev),
95267
96053
  revBy: deviceState.deviceId
@@ -95338,7 +96124,7 @@ var createAreaActions = ({
95338
96124
  };
95339
96125
  });
95340
96126
  const newAllTasks = state._allTasks.map((task) => {
95341
- if (task.areaId !== id2 || task.deletedAt)
96127
+ if (task.areaId !== id2)
95342
96128
  return task;
95343
96129
  return {
95344
96130
  ...task,
@@ -95665,6 +96451,7 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95665
96451
  color,
95666
96452
  initialProps,
95667
96453
  existingProjects,
96454
+ existingAreas,
95668
96455
  settings,
95669
96456
  deviceId,
95670
96457
  now: now3,
@@ -95672,11 +96459,11 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95672
96459
  }) => {
95673
96460
  const trimmedTitle = typeof title === "string" ? title.trim() : "";
95674
96461
  const targetAreaId = initialProps?.areaId;
95675
- 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);
95676
96463
  const baseOrder = Number.isFinite(initialProps?.order) ? initialProps?.order : maxOrder + 1;
95677
96464
  const hasExplicitFlowMode = Boolean(initialProps && Object.prototype.hasOwnProperty.call(initialProps, "isSequential"));
95678
96465
  const useSequentialDefault = !hasExplicitFlowMode && settings.gtd?.defaultProjectFlowMode === "sequential";
95679
- return {
96466
+ const project = {
95680
96467
  id: id2 ?? generateUUID(),
95681
96468
  title: trimmedTitle,
95682
96469
  color: color ?? DEFAULT_PROJECT_COLOR,
@@ -95686,10 +96473,14 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95686
96473
  revBy: deviceId,
95687
96474
  createdAt: now3,
95688
96475
  updatedAt: now3,
96476
+ isSequential: false,
96477
+ isFocused: false,
95689
96478
  ...useSequentialDefault ? { isSequential: true } : {},
95690
96479
  ...initialProps,
95691
96480
  tagIds: initialProps?.tagIds ?? []
95692
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 };
95693
96484
  }, createProjectCoreActions = ({
95694
96485
  set: set5,
95695
96486
  get,
@@ -95718,6 +96509,7 @@ var duplicateProjectAttachmentCopy = (attachment, now3) => ({
95718
96509
  color,
95719
96510
  initialProps,
95720
96511
  existingProjects: state._allProjects,
96512
+ existingAreas: state._allAreas,
95721
96513
  settings: state.settings,
95722
96514
  deviceId: deviceState.deviceId,
95723
96515
  now: now3
@@ -96399,7 +97191,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96399
97191
  const now3 = new Date().toISOString();
96400
97192
  const projectOrderReserver = createProjectOrderReserver(currentState._allTasks);
96401
97193
  const focusTaskLimit = normalizeFocusTaskLimit(currentState.settings.gtd?.focusTaskLimit);
96402
- let focusedCount = currentState.getDerivedState().focusedCount;
97194
+ let focusedCount = currentState.getFocusedCount();
96403
97195
  const nextAllTasks = [...currentState._allTasks];
96404
97196
  const newTasks = [];
96405
97197
  for (const item of normalizedItems) {
@@ -96445,6 +97237,8 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96445
97237
  updatedAt: now3,
96446
97238
  deletedAt: undefined,
96447
97239
  purgedAt: undefined,
97240
+ isFocusedToday: initialTaskProps.isFocusedToday ?? false,
97241
+ suppressMindwtrReminders: initialTaskProps.suppressMindwtrReminders ?? false,
96448
97242
  ...referenceClears,
96449
97243
  areaId: resolvedAreaId,
96450
97244
  projectId: resolvedProjectId,
@@ -96513,7 +97307,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96513
97307
  const isPromotingTaskFocus = preparedUpdates.updates.isFocusedToday === true && existingTask.isFocusedToday !== true;
96514
97308
  if (isPromotingTaskFocus) {
96515
97309
  const focusTaskLimit = normalizeFocusTaskLimit(currentState.settings.gtd?.focusTaskLimit);
96516
- const focusedCount = currentState.getDerivedState().focusedCount;
97310
+ const focusedCount = currentState.getFocusedCount();
96517
97311
  if (focusedCount >= focusTaskLimit) {
96518
97312
  const message3 = `Focus limit of ${focusTaskLimit} reached`;
96519
97313
  set5({ error: message3 });
@@ -96777,35 +97571,109 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96777
97571
  return missingTask ? actionFail2("Task not found") : actionOk2({ id: duplicatedTaskId });
96778
97572
  },
96779
97573
  convertTaskToSection: async (id2) => {
96780
- const sourceTask = get()._tasksById.get(id2);
96781
- if (!sourceTask || sourceTask.deletedAt)
96782
- return actionFail2("Task not found");
96783
- const projectId = sourceTask.projectId;
96784
- if (!projectId)
96785
- return actionFail2("Task is not in a project");
96786
- const description = typeof sourceTask.description === "string" ? sourceTask.description.trim() : "";
96787
- const section = await get().addSection(projectId, sourceTask.title, description ? { description } : undefined);
96788
- if (!section)
96789
- return actionFail2("Section could not be created");
97574
+ const changeAt = Date.now();
96790
97575
  const now3 = new Date().toISOString();
96791
- const checklistTasks = (sourceTask.checklist || []).filter((item) => typeof item.title === "string" && item.title.trim().length > 0).map((item) => ({
96792
- title: item.title,
96793
- initialProps: {
97576
+ let errorMessage;
97577
+ let convertedSectionId;
97578
+ set5((state) => {
97579
+ const sourceTask = state._tasksById.get(id2);
97580
+ if (!sourceTask || sourceTask.deletedAt) {
97581
+ errorMessage = "Task not found";
97582
+ return state;
97583
+ }
97584
+ const projectId = normalizeOptionalContainerId(sourceTask.projectId);
97585
+ if (!projectId) {
97586
+ errorMessage = "Task is not in a project";
97587
+ return state;
97588
+ }
97589
+ const projectExists = state._allProjects.some((project) => project.id === projectId && !project.deletedAt);
97590
+ const sectionTitle = typeof sourceTask.title === "string" ? sourceTask.title.trim() : "";
97591
+ if (!projectExists || !sectionTitle) {
97592
+ errorMessage = "Section could not be created";
97593
+ return state;
97594
+ }
97595
+ const deviceState = ensureDeviceId(state.settings);
97596
+ const sectionOrder = state._allSections.filter((section2) => section2.projectId === projectId && !section2.deletedAt).reduce((max3, section2) => Math.max(max3, Number.isFinite(section2.order) ? section2.order : -1), -1) + 1;
97597
+ const description = typeof sourceTask.description === "string" ? sourceTask.description.trim() : "";
97598
+ const section = {
97599
+ id: generateUUID(),
96794
97600
  projectId,
96795
- sectionId: section.id,
96796
- status: item.isCompleted ? "done" : "next",
96797
- ...item.isCompleted ? { completedAt: now3 } : {}
97601
+ title: sectionTitle,
97602
+ ...description ? { description } : {},
97603
+ order: sectionOrder,
97604
+ isCollapsed: false,
97605
+ rev: 1,
97606
+ revBy: deviceState.deviceId,
97607
+ createdAt: now3,
97608
+ updatedAt: now3
97609
+ };
97610
+ const nextAllSections = [...state._allSections, section];
97611
+ const projectOrderReserver = createProjectOrderReserver(state._allTasks);
97612
+ const checklistTasks = [];
97613
+ for (const item of sourceTask.checklist || []) {
97614
+ const title = typeof item.title === "string" ? item.title.trim() : "";
97615
+ if (!title)
97616
+ continue;
97617
+ const containerResolution = resolveTaskContainerAssignment({
97618
+ projectId,
97619
+ sectionId: section.id,
97620
+ areaId: undefined,
97621
+ allProjects: state._allProjects,
97622
+ allSections: nextAllSections,
97623
+ allAreas: state._allAreas
97624
+ });
97625
+ if (!containerResolution.ok) {
97626
+ errorMessage = containerResolution.error;
97627
+ return state;
97628
+ }
97629
+ const order = projectOrderReserver(containerResolution.projectId);
97630
+ checklistTasks.push({
97631
+ id: generateUUID(),
97632
+ title,
97633
+ status: item.isCompleted ? "done" : "next",
97634
+ taskMode: "task",
97635
+ tags: [],
97636
+ contexts: [],
97637
+ pushCount: 0,
97638
+ projectId: containerResolution.projectId,
97639
+ sectionId: containerResolution.sectionId,
97640
+ areaId: containerResolution.areaId,
97641
+ ...item.isCompleted ? { completedAt: now3 } : {},
97642
+ order,
97643
+ orderNum: order,
97644
+ rev: 1,
97645
+ revBy: deviceState.deviceId,
97646
+ createdAt: now3,
97647
+ updatedAt: now3
97648
+ });
96798
97649
  }
96799
- }));
96800
- if (checklistTasks.length > 0) {
96801
- const added = await get().addTasks(checklistTasks);
96802
- if (!added.success)
96803
- return added;
96804
- }
96805
- const deleted = await get().deleteTask(id2);
96806
- if (!deleted.success)
96807
- return deleted;
96808
- return actionOk2({ id: section.id });
97650
+ const deletedSource = {
97651
+ ...sourceTask,
97652
+ deletedAt: now3,
97653
+ updatedAt: now3,
97654
+ rev: nextRevision(sourceTask.rev),
97655
+ revBy: deviceState.deviceId
97656
+ };
97657
+ const nextAllTasks = [
97658
+ ...replaceEntityInArray(state._allTasks, deletedSource.id, deletedSource),
97659
+ ...checklistTasks
97660
+ ];
97661
+ convertedSectionId = section.id;
97662
+ persist(set5, debouncedSave, state, {
97663
+ tasks: nextAllTasks,
97664
+ sections: nextAllSections,
97665
+ ...deviceState.updated ? { settings: deviceState.settings } : {}
97666
+ });
97667
+ return {
97668
+ _allTasks: nextAllTasks,
97669
+ _allSections: nextAllSections,
97670
+ lastDataChangeAt: getNextDataChangeAt(state.lastDataChangeAt, changeAt),
97671
+ ...deviceState.updated ? { settings: deviceState.settings } : {}
97672
+ };
97673
+ });
97674
+ if (errorMessage)
97675
+ return actionFail2(errorMessage);
97676
+ return convertedSectionId ? actionOk2({ id: convertedSectionId }) : actionFail2("Task not found");
96809
97677
  },
96810
97678
  promoteTaskToProject: async (id2, options) => {
96811
97679
  const changeAt = Date.now();
@@ -96850,6 +97718,7 @@ var SLOW_TASK_UPDATE_LOG_THRESHOLD_MS = 500, collectAttachmentCloudKeysForTasks
96850
97718
  tagIds: projectTagIds
96851
97719
  },
96852
97720
  existingProjects: state._allProjects,
97721
+ existingAreas: state._allAreas,
96853
97722
  settings: state.settings,
96854
97723
  deviceId: deviceState.deviceId,
96855
97724
  now: now3
@@ -98495,7 +99364,7 @@ var DEFAULT_ATTACHMENT_CLEANUP_INTERVAL_MS, CLOUD_PROVIDER_SELF_HOSTED = "selfho
98495
99364
  return currentChangeAt;
98496
99365
  onStale?.({ localSnapshotChangeAt, currentChangeAt });
98497
99366
  requestFollowUp();
98498
- throw new LocalSyncAbort;
99367
+ throw new LocalSyncAbort("local-data-changed");
98499
99368
  }, getInMemoryAppDataSnapshot = () => {
98500
99369
  const state = useTaskStore.getState();
98501
99370
  return cloneAppData({
@@ -98576,9 +99445,11 @@ var init_sync_client_helpers = __esm(() => {
98576
99445
  init_sync_runtime_utils();
98577
99446
  DEFAULT_ATTACHMENT_CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
98578
99447
  LocalSyncAbort = class LocalSyncAbort extends Error {
98579
- constructor() {
99448
+ reason;
99449
+ constructor(reason = "local-data-changed") {
98580
99450
  super("Local changes detected during sync");
98581
99451
  this.name = "LocalSyncAbort";
99452
+ this.reason = reason;
98582
99453
  }
98583
99454
  };
98584
99455
  });
@@ -100659,21 +101530,26 @@ function getSyncEncryptionStatusFromLocalState(localState) {
100659
101530
  incompleteTransition: persisted.incompleteTransition
100660
101531
  };
100661
101532
  }
100662
- function markRemoteEncryptionDiscovered(localState, discovered) {
101533
+ function markRemoteEncryptionDiscovered(localState, discovered, scope) {
100663
101534
  const current = localState.read();
100664
101535
  if (current && SYNC_ENCRYPTION_KEYED_STATES.includes(current.state) && current.discoveredSalt === bytesToHex(discovered.salt))
100665
101536
  return;
100666
101537
  localState.write({
100667
101538
  state: "remote-encrypted-no-key",
100668
101539
  discoveredSalt: bytesToHex(discovered.salt),
100669
- discoveredParams: discovered.params
101540
+ discoveredParams: discovered.params,
101541
+ ...scope ? { discoveredScope: scope } : {}
100670
101542
  });
100671
101543
  }
100672
- function markRemotePlaintextDiscovered(localState) {
101544
+ function markRemotePlaintextDiscovered(localState, scope) {
100673
101545
  const current = localState.read();
100674
101546
  if (!current || current.state !== "enabled")
100675
101547
  return;
100676
- localState.write({ ...current, state: "remote-plaintext" });
101548
+ localState.write({
101549
+ ...current,
101550
+ state: "remote-plaintext",
101551
+ ...scope ? { discoveredScope: scope } : {}
101552
+ });
100677
101553
  }
100678
101554
  function reaffirmRemoteEncryptionNoKey(localState) {
100679
101555
  const current = localState.read();
@@ -101082,6 +101958,10 @@ async function runProvideSyncEncryptionPassphraseOverRemote(passphrase, baseDocu
101082
101958
  const captured = await remote.read(encName);
101083
101959
  const bytes = captured.bytes;
101084
101960
  if (!bytes) {
101961
+ if (localState.read()?.state === "remote-encrypted-no-key") {
101962
+ await runDisableSyncEncryptionLocalOnly(keyCache, localState);
101963
+ return "no-encrypted-remote";
101964
+ }
101085
101965
  throw new Error(`sync encryption: no encrypted remote artifact found at ${encName}`);
101086
101966
  }
101087
101967
  requireExistingRemoteVersion(encName, captured);
@@ -101117,7 +101997,42 @@ async function runProvideSyncEncryptionPassphraseOverRemote(passphrase, baseDocu
101117
101997
  }
101118
101998
  throw new SyncEncryptionRemoteConflictError(`${encName} changed during passphrase validation`);
101119
101999
  }
101120
- 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) => {
101121
102036
  const current = localState.read();
101122
102037
  if (current?.incompleteTransition && current.incompleteTransition !== kind) {
101123
102038
  throw new SyncEncryptionTransitionIncompleteError(current.incompleteTransition);
@@ -101193,6 +102108,7 @@ var SYNC_ENCRYPTION_KEYED_STATES, SYNC_ENCRYPTION_TRANSITION_INCOMPLETE = "SYNC_
101193
102108
  }, PLAINTEXT_PAD_BYTE = 32, __syncEncryptionTestUtils;
101194
102109
  var init_sync_encryption = __esm(() => {
101195
102110
  init_sync_crypto();
102111
+ init_sync_helpers();
101196
102112
  SYNC_ENCRYPTION_KEYED_STATES = ["enabled", "remote-plaintext"];
101197
102113
  SyncEncryptionTransitionIncompleteError = class SyncEncryptionTransitionIncompleteError extends Error {
101198
102114
  constructor(kind) {
@@ -101557,8 +102473,17 @@ async function webdavGetFileVersionedWithServerTime(url, options = {}) {
101557
102473
  error2.status = res.status;
101558
102474
  throw error2;
101559
102475
  }
102476
+ let body;
102477
+ try {
102478
+ body = await readResponseBody(res, options.onProgress, options.maxBytes ?? MAX_DOWNLOAD_BYTES, signal);
102479
+ } catch (error2) {
102480
+ if (options.treatOversizeAsAbsent && error2 instanceof ResponseTooLargeError) {
102481
+ return { bytes: null, version: null, serverNowMs };
102482
+ }
102483
+ throw error2;
102484
+ }
101560
102485
  return {
101561
- bytes: new Uint8Array(await readResponseBody(res, options.onProgress, options.maxBytes ?? MAX_DOWNLOAD_BYTES, signal)),
102486
+ bytes: new Uint8Array(body),
101562
102487
  version: normalizeStrongWebdavEtag(res.headers.get("etag")),
101563
102488
  serverNowMs
101564
102489
  };
@@ -101593,7 +102518,14 @@ async function probeWebdavSyncCompatibility(documentUrl, options = {}, policy =
101593
102518
  }
101594
102519
  return "legacy-plaintext";
101595
102520
  }
101596
- await assertWebdavConditionalWriteSupport(documentUrl, documentOptions);
102521
+ try {
102522
+ await assertWebdavConditionalWriteSupport(documentUrl, documentOptions);
102523
+ } catch (error2) {
102524
+ if (!policy.requireStrongEtag && error2 instanceof SyncEncryptionRemoteVersionUnavailableError) {
102525
+ return "legacy-plaintext";
102526
+ }
102527
+ throw error2;
102528
+ }
101597
102529
  return "strong-etag";
101598
102530
  }
101599
102531
  async function assertWebdavStrongEtagSupport(documentUrl, options = {}) {
@@ -101789,15 +102721,18 @@ var WEBDAV_REMOTE_WRITE_CONFLICT = "WEBDAV_REMOTE_WRITE_CONFLICT", WebDavRemoteW
101789
102721
  }
101790
102722
  }, __webdavTestUtils, getWebdavParentCollectionUrl = (url) => {
101791
102723
  try {
101792
- const parsed = new URL(url);
101793
- const trimmedPath = parsed.pathname.replace(/\/+$/, "");
101794
- const lastSlash = trimmedPath.lastIndexOf("/");
101795
- 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)
101796
102728
  return null;
101797
- parsed.pathname = trimmedPath.slice(0, lastSlash);
101798
- parsed.search = "";
101799
- parsed.hash = "";
101800
- 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);
101801
102736
  } catch {
101802
102737
  return null;
101803
102738
  }
@@ -102102,7 +103037,7 @@ async function acquireSyncRemoteMutationFence(port, options) {
102102
103037
  if (!Number.isFinite(ttlMs) || ttlMs < MIN_TTL_MS || ttlMs > MAX_TTL_MS) {
102103
103038
  throw new Error(`Remote sync mutation fence ttlMs must be between ${MIN_TTL_MS} and ${MAX_TTL_MS}`);
102104
103039
  }
102105
- 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)));
102106
103041
  if (!Number.isFinite(heartbeatMs) || heartbeatMs < 0 || heartbeatMs >= ttlMs) {
102107
103042
  throw new Error("Remote sync mutation fence heartbeatMs must be zero or shorter than ttlMs");
102108
103043
  }
@@ -102115,16 +103050,27 @@ async function acquireSyncRemoteMutationFence(port, options) {
102115
103050
  throw new Error("Remote sync mutation fence leaseId is too short");
102116
103051
  let acquiredVersion = null;
102117
103052
  let acquiredRemainingMs = ttlMs;
103053
+ let reclaimedFrom = null;
102118
103054
  for (let attempt = 0;attempt < maxAttempts; attempt += 1) {
102119
103055
  const snapshot = await port.read();
102120
103056
  const serverNowMs = requireServerNow(snapshot);
103057
+ reclaimedFrom = null;
102121
103058
  if (snapshot.bytes) {
102122
103059
  if (!snapshot.version) {
102123
103060
  throw new SyncRemoteMutationFenceUnavailableError("Existing remote sync mutation fence has no safe version");
102124
103061
  }
102125
103062
  const current = parseRecord(snapshot.bytes);
102126
103063
  if (current.expiresAt > serverNowMs && !isImpossibleFutureExpiry(current.expiresAt, serverNowMs)) {
102127
- 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;
102128
103074
  }
102129
103075
  } else if (snapshot.version !== null) {
102130
103076
  throw new SyncRemoteMutationFenceUnavailableError("Missing remote sync mutation fence unexpectedly has a version");
@@ -102134,7 +103080,9 @@ async function acquireSyncRemoteMutationFence(port, options) {
102134
103080
  leaseId,
102135
103081
  ownerId,
102136
103082
  purpose: options.purpose,
102137
- expiresAt: serverNowMs + ttlMs
103083
+ expiresAt: serverNowMs + ttlMs,
103084
+ heartbeatMs,
103085
+ renewedAt: serverNowMs
102138
103086
  };
102139
103087
  try {
102140
103088
  await port.write(encodeRecord(record3), snapshot.version);
@@ -102206,7 +103154,9 @@ async function acquireSyncRemoteMutationFence(port, options) {
102206
103154
  leaseId,
102207
103155
  ownerId,
102208
103156
  purpose: options.purpose,
102209
- expiresAt: serverNowMs + ttlMs
103157
+ expiresAt: serverNowMs + ttlMs,
103158
+ heartbeatMs,
103159
+ renewedAt: serverNowMs
102210
103160
  };
102211
103161
  try {
102212
103162
  await port.write(encodeRecord(replacement), snapshot.version);
@@ -102248,6 +103198,7 @@ async function acquireSyncRemoteMutationFence(port, options) {
102248
103198
  };
102249
103199
  scheduleHeartbeat();
102250
103200
  return {
103201
+ reclaimedFrom,
102251
103202
  assertHeld: (minRemainingMs = 0) => serialize(async () => {
102252
103203
  if (!Number.isFinite(minRemainingMs) || minRemainingMs < 0 || minRemainingMs >= ttlMs) {
102253
103204
  throw new Error("Remote sync mutation fence remaining-time requirement is invalid");
@@ -102289,12 +103240,19 @@ async function acquireSyncRemoteMutationFence(port, options) {
102289
103240
  })
102290
103241
  };
102291
103242
  }
102292
- 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) => {
102293
103244
  if (snapshot.serverNowMs === null || !Number.isFinite(snapshot.serverNowMs)) {
102294
103245
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fencing requires a valid provider Date response header");
102295
103246
  }
102296
103247
  return snapshot.serverNowMs;
102297
- }, 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) => {
102298
103256
  if (bytes.length === 0 || bytes.length > MAX_RECORD_BYTES) {
102299
103257
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fence record is malformed");
102300
103258
  }
@@ -102311,7 +103269,14 @@ var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE
102311
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)) {
102312
103270
  throw new SyncRemoteMutationFenceUnavailableError("Remote sync mutation fence record is malformed");
102313
103271
  }
102314
- 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;
102315
103280
  }, encodeRecord = (record3) => encodeUtf8(JSON.stringify(record3)), randomLeaseId = () => {
102316
103281
  const randomUuid = globalThis.crypto?.randomUUID?.();
102317
103282
  if (randomUuid)
@@ -102321,10 +103286,12 @@ var SYNC_REMOTE_MUTATION_FENCE_NAME = ".mindwtr-sync-fence-v1.json", SYNC_REMOTE
102321
103286
  var init_sync_remote_fence = __esm(() => {
102322
103287
  SyncRemoteMutationFenceBusyError = class SyncRemoteMutationFenceBusyError extends Error {
102323
103288
  retryAfterMs;
102324
- constructor(retryAfterMs) {
102325
- 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");
102326
103292
  this.name = "SyncRemoteMutationFenceBusyError";
102327
103293
  this.retryAfterMs = Math.max(0, Math.floor(retryAfterMs));
103294
+ this.holder = holder;
102328
103295
  }
102329
103296
  };
102330
103297
  SyncRemoteMutationFenceLostError = class SyncRemoteMutationFenceLostError extends Error {
@@ -102355,6 +103322,7 @@ class SharedSyncRunMachine {
102355
103322
  performSyncCycleImpl;
102356
103323
  io = null;
102357
103324
  remoteMutationFence = null;
103325
+ carriedIdleSnapshot = takeIdleCycleSnapshot();
102358
103326
  state = {
102359
103327
  backend: "off",
102360
103328
  cloudProvider: "selfhosted",
@@ -102362,10 +103330,14 @@ class SharedSyncRunMachine {
102362
103330
  fastSyncScope: null,
102363
103331
  localSnapshotChangeAt: 0,
102364
103332
  localDataCache: null,
103333
+ localSnapshotMatchesDisk: false,
103334
+ localDocumentFingerprint: null,
103335
+ fastSyncStateCache: null,
102365
103336
  preSyncedLocalData: null,
102366
103337
  wroteLocal: false,
102367
103338
  remoteDataForCompare: null,
102368
103339
  readCheckRemoteData: undefined,
103340
+ localOnlyUploadFingerprint: null,
102369
103341
  lastRemoteWriteFingerprint: null,
102370
103342
  lastRemoteWriteMergedServerData: false,
102371
103343
  webdavRemoteCorrupted: false,
@@ -102431,10 +103403,14 @@ class SharedSyncRunMachine {
102431
103403
  await this.runAttachmentPreSyncPhase();
102432
103404
  }
102433
103405
  let skipResult = null;
103406
+ let localOnlyUpload = false;
102434
103407
  if (!this.options.activationProbe) {
102435
103408
  skipResult = await this.trySkipUnchangedFastSync();
103409
+ if (!skipResult) {
103410
+ localOnlyUpload = await this.tryArmLocalOnlyUploadFastPath();
103411
+ }
102436
103412
  }
102437
- if (!this.options.activationProbe && !skipResult && this.policy.enableReadCheckSkip) {
103413
+ if (!this.options.activationProbe && !skipResult && !localOnlyUpload && this.policy.enableReadCheckSkip) {
102438
103414
  skipResult = await this.trySkipUnchangedReadSync();
102439
103415
  }
102440
103416
  if (skipResult) {
@@ -102478,10 +103454,18 @@ class SharedSyncRunMachine {
102478
103454
  const lease = await this.runRemoteMutationFenceOperation("Remote sync mutation fence acquisition failed", acquire);
102479
103455
  if (!lease)
102480
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
+ }
102481
103461
  this.remoteMutationFence = lease;
102482
103462
  this.state.readCheckRemoteData = undefined;
102483
103463
  this.state.remoteDataForCompare = null;
102484
103464
  }
103465
+ async acquireAndAssertRemoteMutationFence(minRemainingMs) {
103466
+ await this.ensureRemoteMutationFence();
103467
+ await this.assertRemoteMutationFenceHeld(minRemainingMs);
103468
+ }
102485
103469
  async assertRemoteMutationFenceHeld(minRemainingMs = SYNC_REMOTE_MUTATION_REQUEST_HORIZON_MS) {
102486
103470
  if (!this.remoteMutationFence)
102487
103471
  return;
@@ -102529,7 +103513,7 @@ class SharedSyncRunMachine {
102529
103513
  attachmentHelpers(phase) {
102530
103514
  return {
102531
103515
  ensureLocalSnapshotFresh: () => this.ensureLocalSnapshotFresh(),
102532
- assertRemoteMutationFenceHeld: (minRemainingMs) => this.assertRemoteMutationFenceHeld(minRemainingMs),
103516
+ assertRemoteMutationFenceHeld: (minRemainingMs) => this.acquireAndAssertRemoteMutationFence(minRemainingMs),
102533
103517
  activationProbe: this.options.activationProbe === true,
102534
103518
  phase
102535
103519
  };
@@ -102565,15 +103549,38 @@ class SharedSyncRunMachine {
102565
103549
  this.state.localSnapshotChangeAt = currentChangeAt;
102566
103550
  return this.state.localDataCache.data;
102567
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
+ }
102568
103565
  const inMemorySnapshot = this.store.getInMemorySnapshot();
102569
103566
  let baseData;
103567
+ let matchesDisk = false;
102570
103568
  if (this.state.preSyncedLocalData) {
102571
- 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
+ });
102572
103578
  } else {
102573
103579
  const persisted = await this.storage.readPersistedLocal();
102574
103580
  const reconcileStart = Date.now();
102575
103581
  const aligned = computeSyncChangeFingerprint(persisted) === computeSyncChangeFingerprint(inMemorySnapshot);
102576
103582
  baseData = aligned ? persisted : mergeAppData(persisted, inMemorySnapshot);
103583
+ matchesDisk = aligned;
102577
103584
  this.notifier.logInfo("Sync local reconcile", {
102578
103585
  reconcile: aligned ? "aligned-skip" : "merged",
102579
103586
  durationMs: String(Date.now() - reconcileStart),
@@ -102586,12 +103593,14 @@ class SharedSyncRunMachine {
102586
103593
  changeAt: currentChangeAt,
102587
103594
  data
102588
103595
  };
103596
+ this.state.localSnapshotMatchesDisk = matchesDisk && data === baseData;
102589
103597
  return data;
102590
103598
  }
102591
103599
  async persistLocalDataWithTracking(data) {
102592
103600
  await this.assertRemoteMutationFenceHeld();
102593
103601
  const persisted = await this.storage.persistLocal(data) ?? data;
102594
103602
  this.ensureLocalSnapshotFresh(persisted);
103603
+ this.state.localSnapshotMatchesDisk = false;
102595
103604
  if (this.storage.applyDataToStore) {
102596
103605
  this.storage.applyDataToStore(persisted);
102597
103606
  const currentChangeAt = this.store.getLastDataChangeAt();
@@ -102622,6 +103631,10 @@ class SharedSyncRunMachine {
102622
103631
  this.state.remoteDataForCompare = data;
102623
103632
  return data;
102624
103633
  }
103634
+ if (this.state.localOnlyUploadFingerprint) {
103635
+ this.state.remoteDataForCompare = null;
103636
+ return null;
103637
+ }
102625
103638
  await this.ensureNetwork();
102626
103639
  try {
102627
103640
  const raw = await this.requireIo().readRemote();
@@ -102652,7 +103665,23 @@ class SharedSyncRunMachine {
102652
103665
  return null;
102653
103666
  return io.readRemoteFingerprint();
102654
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
+ }
102655
103683
  async writeRemoteForCycle(data) {
103684
+ this.assertLocalOnlyUploadIsCanonical(data);
102656
103685
  await this.ensureRemoteMutationFence();
102657
103686
  await this.assertRemoteMutationFenceHeld();
102658
103687
  await this.ensureNetwork();
@@ -102686,7 +103715,7 @@ class SharedSyncRunMachine {
102686
103715
  } catch (error2) {
102687
103716
  if (error2 instanceof SyncRemoteWriteConflict) {
102688
103717
  this.requestFollowUp();
102689
- throw new LocalSyncAbort;
103718
+ throw new LocalSyncAbort("remote-write-conflict");
102690
103719
  }
102691
103720
  throw error2;
102692
103721
  }
@@ -102736,8 +103765,8 @@ class SharedSyncRunMachine {
102736
103765
  this.ensureLocalSnapshotFresh();
102737
103766
  if (hasPendingSyncSideEffects(localData))
102738
103767
  return null;
102739
- const localFingerprint = computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(localData));
102740
- const cached2 = await this.storage.readFastSyncState(scope);
103768
+ const localFingerprint = this.localDocumentFingerprint(localData);
103769
+ const cached2 = await this.readFastSyncState(scope);
102741
103770
  if (!cached2 || cached2.localFingerprint !== localFingerprint)
102742
103771
  return null;
102743
103772
  let remoteFingerprint = null;
@@ -102750,16 +103779,64 @@ class SharedSyncRunMachine {
102750
103779
  if (!remoteFingerprint || remoteFingerprint !== cached2.remoteFingerprint)
102751
103780
  return null;
102752
103781
  this.ensureLocalSnapshotFresh();
102753
- await this.storage.writeFastSyncState({
103782
+ await this.writeFastSyncState({
102754
103783
  scope,
102755
103784
  localFingerprint,
102756
103785
  remoteFingerprint,
102757
103786
  checkedAt: this.nowIso()
102758
103787
  });
102759
103788
  await this.persistUnchangedSyncStatus();
103789
+ this.publishIdleCycleSnapshot(localData, localFingerprint);
102760
103790
  this.notifier.logInfo("Sync fast check found no changes", { backend: this.backend });
103791
+ this.logUnchangedCycleSkips("fast");
102761
103792
  return { success: true, skipped: "unchanged" };
102762
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
+ }
102763
103840
  async trySkipUnchangedReadSync() {
102764
103841
  this.setStep("read-check");
102765
103842
  await this.yieldToUi();
@@ -102776,14 +103853,16 @@ class SharedSyncRunMachine {
102776
103853
  this.state.readCheckRemoteData = remoteData;
102777
103854
  if (hasUncompactedPurgedTombstones(remoteData))
102778
103855
  return null;
102779
- const localDocument = toRemoteSyncDocument(localData);
102780
- const remoteDocument = toRemoteSyncDocument(remoteData);
102781
- if (!areRemoteSyncDocumentsEqual(remoteDocument, localDocument))
103856
+ const localFingerprint = this.localDocumentFingerprint(localData);
103857
+ const remoteFingerprint = computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(remoteData));
103858
+ if (localFingerprint !== remoteFingerprint)
102782
103859
  return null;
102783
103860
  await this.recordFastSyncState(localData, { allowRemoteFingerprintRead: false });
102784
103861
  await this.persistUnchangedSyncStatus();
103862
+ this.publishIdleCycleSnapshot(localData, localFingerprint);
102785
103863
  this.state.readCheckRemoteData = undefined;
102786
103864
  this.notifier.logInfo("Sync read check found no changes", { backend: this.backend });
103865
+ this.logUnchangedCycleSkips("read");
102787
103866
  return { success: true, skipped: "unchanged" };
102788
103867
  }
102789
103868
  async recordFastSyncState(data, options = {}) {
@@ -102810,13 +103889,71 @@ class SharedSyncRunMachine {
102810
103889
  }
102811
103890
  if (!remoteFingerprint)
102812
103891
  return;
102813
- await this.storage.writeFastSyncState({
103892
+ await this.writeFastSyncState({
102814
103893
  scope,
102815
- localFingerprint: computeRemoteSyncDocumentFingerprint(toRemoteSyncDocument(data)),
103894
+ localFingerprint: this.localDocumentFingerprint(data),
102816
103895
  remoteFingerprint,
102817
103896
  checkedAt: this.nowIso()
102818
103897
  });
102819
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
+ }
102820
103957
  async runAttachmentPreSyncPhase() {
102821
103958
  if (!this.policy.attachmentPhasesEnabled || this.options.activationProbe)
102822
103959
  return;
@@ -102833,7 +103970,6 @@ class SharedSyncRunMachine {
102833
103970
  if (isRemoteSyncBackend(this.backend)) {
102834
103971
  await this.ensureNetwork();
102835
103972
  }
102836
- await this.ensureRemoteMutationFence();
102837
103973
  const result = await io.syncAttachments(localData, this.attachmentHelpers("prepare"));
102838
103974
  await this.assertRemoteMutationFenceHeld();
102839
103975
  const mutated = result === true || Boolean(result) && typeof result === "object";
@@ -102865,7 +104001,7 @@ class SharedSyncRunMachine {
102865
104001
  }
102866
104002
  async prepareRemoteWriteData(data) {
102867
104003
  if (this.options.activationProbe) {
102868
- const activationSnapshot = prepareActivationAttachmentSnapshot(data, this.state.remoteDataForCompare);
104004
+ const activationSnapshot = prepareActivationAttachmentSnapshot(data, this.state.remoteDataForCompare, this.state.localDataCache?.data ?? null);
102869
104005
  if (activationSnapshot.count === 0)
102870
104006
  return data;
102871
104007
  const io2 = this.requireIo();
@@ -102878,15 +104014,25 @@ class SharedSyncRunMachine {
102878
104014
  await this.ensureNetwork();
102879
104015
  }
102880
104016
  await this.ensureRemoteMutationFence();
102881
- const result2 = await io2.syncAttachments(activationSnapshot.data, this.attachmentHelpers("post-merge"));
104017
+ let result2 = await io2.syncAttachments(activationSnapshot.data, this.attachmentHelpers("post-merge"));
102882
104018
  await this.assertRemoteMutationFenceHeld();
102883
- const provenData = result2 && typeof result2 === "object" ? result2 : activationSnapshot.data;
102884
- assertActivationAttachmentsProven(provenData, activationSnapshot.count);
104019
+ let provenData = result2 && typeof result2 === "object" ? result2 : activationSnapshot.data;
104020
+ this.ensureLocalSnapshotFresh();
104021
+ const fallbackRetry = prepareActivationFallbackRetry(provenData, activationSnapshot.localFallbacks);
104022
+ if (fallbackRetry.count > 0) {
104023
+ result2 = await io2.syncAttachments(fallbackRetry.data, this.attachmentHelpers("post-merge"));
104024
+ await this.assertRemoteMutationFenceHeld();
104025
+ provenData = result2 && typeof result2 === "object" ? result2 : fallbackRetry.data;
104026
+ }
104027
+ assertActivationAttachmentsProven(provenData, activationSnapshot.expectedIds, activationSnapshot.metadataOnlyIds, activationSnapshot.noLocalBytesIds);
102885
104028
  this.ensureLocalSnapshotFresh();
102886
104029
  this.notifier.onDiagnostic?.({
102887
104030
  event: "attachments-prepare-complete",
102888
104031
  data: provenData,
102889
- extra: { mutated: String(result2 === true || Boolean(result2 && typeof result2 === "object")) }
104032
+ extra: {
104033
+ mutated: String(result2 === true || Boolean(result2 && typeof result2 === "object")),
104034
+ fallbackRetries: String(fallbackRetry.count)
104035
+ }
102890
104036
  });
102891
104037
  return provenData;
102892
104038
  }
@@ -103010,6 +104156,19 @@ class SharedSyncRunMachine {
103010
104156
  });
103011
104157
  },
103012
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
+ },
103013
104172
  prepareRemoteWrite: (data) => this.prepareRemoteWriteData(data),
103014
104173
  writeRemote: async (data) => {
103015
104174
  this.notifier.tracePayload?.("write-remote", data, { backend: this.backend });
@@ -103017,6 +104176,7 @@ class SharedSyncRunMachine {
103017
104176
  await this.writeRemoteForCycle(data);
103018
104177
  },
103019
104178
  preferIncomingAttachmentCloudKeys: this.options.ignorePendingRemoteWriteBackoff === true,
104179
+ skipEmptyRemoteMerge: () => this.state.localOnlyUploadFingerprint !== null,
103020
104180
  onStep: (next) => this.setStep(next),
103021
104181
  yieldToUi: this.notifier.yieldToUi ? () => this.notifier.yieldToUi() : undefined,
103022
104182
  historyContext: {
@@ -103103,9 +104263,11 @@ class SharedSyncRunMachine {
103103
104263
  await this.yieldToUi();
103104
104264
  this.ensureLocalSnapshotFresh(mergedData);
103105
104265
  await this.assertRemoteMutationFenceHeld();
104266
+ const localWriteSkipped = syncResult.localWriteSkipped && !this.state.wroteLocal ? true : undefined;
103106
104267
  await this.hooks.finalizeSuccess(mergedData, {
103107
104268
  status: syncResult.status,
103108
104269
  wroteLocal: this.state.wroteLocal,
104270
+ localWriteSkipped,
103109
104271
  getLocalSnapshotChangeAt: () => this.state.localSnapshotChangeAt,
103110
104272
  acceptCoveredSnapshot: (expectedData) => this.acceptCoveredLocalSnapshot(expectedData)
103111
104273
  });
@@ -103117,6 +104279,7 @@ class SharedSyncRunMachine {
103117
104279
  attachmentWriteDeferred: attachmentWriteDeferred || undefined,
103118
104280
  fileAttachmentUploadBlocked: this.state.fileAttachmentUploadBlocked ?? undefined,
103119
104281
  error: mergedData.settings.lastSyncError,
104282
+ localWriteSkipped,
103120
104283
  stats
103121
104284
  };
103122
104285
  }
@@ -103124,6 +104287,7 @@ class SharedSyncRunMachine {
103124
104287
  success: true,
103125
104288
  attachmentWriteDeferred: attachmentWriteDeferred || undefined,
103126
104289
  fileAttachmentUploadBlocked: this.state.fileAttachmentUploadBlocked ?? undefined,
104290
+ localWriteSkipped,
103127
104291
  stats
103128
104292
  };
103129
104293
  }
@@ -103149,6 +104313,7 @@ class SharedSyncRunMachine {
103149
104313
  };
103150
104314
  }
103151
104315
  if (error2 instanceof SyncRemoteMutationFenceBusyError) {
104316
+ this.notifier.logWarning("Remote sync location is reserved; retrying after the lease lapses", error2);
103152
104317
  if (!this.options.activationProbe)
103153
104318
  this.requestFollowUpAfter(error2.retryAfterMs);
103154
104319
  return {
@@ -103170,6 +104335,12 @@ class SharedSyncRunMachine {
103170
104335
  if (!this.options.activationProbe) {
103171
104336
  await this.persistPreSyncedDataAfterAbort();
103172
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
+ });
103173
104344
  this.notifier.onDiagnostic?.({
103174
104345
  event: "requeued",
103175
104346
  extra: {
@@ -103252,7 +104423,28 @@ var normalizeRemoteWriteResult = (source, result) => {
103252
104423
  fingerprint,
103253
104424
  serverMergedRemoteData: result.serverMergedRemoteData === true
103254
104425
  };
103255
- }, 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) => {
103256
104448
  if (!data.settings.pendingRemoteWriteAt && data.settings.pendingRemoteWriteRetryAt === undefined && data.settings.pendingRemoteWriteAttempts === undefined) {
103257
104449
  return data;
103258
104450
  }
@@ -103296,17 +104488,60 @@ var normalizeRemoteWriteResult = (source, result) => {
103296
104488
  if (!merged.fileHash || !candidate.fileHash)
103297
104489
  return false;
103298
104490
  return merged.fileHash.toLowerCase() !== candidate.fileHash.toLowerCase();
103299
- }, prepareActivationAttachmentSnapshot = (data, candidateRemoteData) => {
104491
+ }, normalizeAttachmentHash = (value) => value?.trim().toLowerCase() ?? "", buildExactLocalActivationFallback = (merged, local, candidate) => {
104492
+ if (!local || !candidate?.cloudKey)
104493
+ return null;
104494
+ const localUri = local.uri?.trim();
104495
+ const localHash = normalizeAttachmentHash(local.fileHash);
104496
+ const mergedHash = normalizeAttachmentHash(merged.fileHash);
104497
+ const candidateHash = normalizeAttachmentHash(candidate.fileHash);
104498
+ if (!localUri || local.localStatus === "missing" || !localHash || localHash !== mergedHash || localHash !== candidateHash || (local.contentRev ?? 0) !== (merged.contentRev ?? 0) || (local.contentRev ?? 0) !== (candidate.contentRev ?? 0)) {
104499
+ return null;
104500
+ }
104501
+ return {
104502
+ ...merged,
104503
+ uri: local.uri,
104504
+ cloudKey: undefined,
104505
+ fileHash: merged.fileHash,
104506
+ contentMtimeMs: local.contentMtimeMs,
104507
+ contentSize: local.contentSize,
104508
+ localStatus: "available",
104509
+ pendingContentUpload: true,
104510
+ deletedAt: undefined
104511
+ };
104512
+ }, prepareActivationAttachmentSnapshot = (data, candidateRemoteData, localData) => {
103300
104513
  const candidateAttachments = new Map;
103301
104514
  if (candidateRemoteData) {
103302
104515
  visitLiveFileAttachments(candidateRemoteData, (attachment) => {
103303
104516
  candidateAttachments.set(attachment.id, attachment);
103304
104517
  });
103305
104518
  }
104519
+ const localAttachments = new Map;
104520
+ if (localData) {
104521
+ visitLiveFileAttachments(localData, (attachment) => {
104522
+ localAttachments.set(attachment.id, attachment);
104523
+ });
104524
+ }
103306
104525
  const candidate = cloneAppData(data);
104526
+ const expectedIds = new Set;
104527
+ const localFallbacks = new Map;
104528
+ const metadataOnlyIds = new Set;
104529
+ const noLocalBytesIds = new Set;
103307
104530
  const count = visitLiveFileAttachments(candidate, (attachment) => {
104531
+ expectedIds.add(attachment.id);
103308
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);
103309
104539
  const mustReplaceCandidateBlob = Boolean(candidateAttachment?.cloudKey && mergedContentMustReplaceCandidateBlob(attachment, candidateAttachment));
104540
+ if (!mustReplaceCandidateBlob) {
104541
+ const fallback = buildExactLocalActivationFallback(attachment, localAttachments.get(attachment.id), candidateAttachment);
104542
+ if (fallback)
104543
+ localFallbacks.set(attachment.id, fallback);
104544
+ }
103310
104545
  attachment.cloudKey = candidateAttachment?.cloudKey;
103311
104546
  attachment.pendingContentUpload = mustReplaceCandidateBlob ? true : undefined;
103312
104547
  if (candidateAttachment?.cloudKey && !mustReplaceCandidateBlob) {
@@ -103314,17 +104549,55 @@ var normalizeRemoteWriteResult = (source, result) => {
103314
104549
  }
103315
104550
  attachment.localStatus = "missing";
103316
104551
  });
103317
- return { data: candidate, count };
103318
- }, assertActivationAttachmentsProven = (data, expectedCount) => {
103319
- let provenCount = 0;
103320
- visitLiveFileAttachments(data, (attachment) => {
103321
- if (!attachment.cloudKey || attachment.localStatus !== "available" || attachment.pendingContentUpload === true) {
103322
- throw new Error(`Candidate attachment proof failed for ${attachment.id}`);
103323
- }
103324
- provenCount += 1;
103325
- });
103326
- if (provenCount !== expectedCount) {
103327
- throw new Error(`Candidate attachment proof incomplete: expected ${expectedCount}, proved ${provenCount}`);
104552
+ return { data: candidate, count, expectedIds, localFallbacks, metadataOnlyIds, noLocalBytesIds };
104553
+ }, prepareActivationFallbackRetry = (data, localFallbacks) => {
104554
+ if (localFallbacks.size === 0)
104555
+ return { data, count: 0 };
104556
+ const retryData = cloneAppData(data);
104557
+ let count = 0;
104558
+ for (const owner of [...retryData.tasks, ...retryData.projects]) {
104559
+ if (owner.deletedAt)
104560
+ continue;
104561
+ for (const attachment of owner.attachments ?? []) {
104562
+ if (attachment.kind !== "file" || !attachment.deletedAt)
104563
+ continue;
104564
+ const fallback = localFallbacks.get(attachment.id);
104565
+ if (!fallback)
104566
+ continue;
104567
+ Object.assign(attachment, fallback);
104568
+ count += 1;
104569
+ }
104570
+ }
104571
+ return count > 0 ? { data: retryData, count } : { data, count: 0 };
104572
+ }, assertActivationAttachmentsProven = (data, expectedIds, metadataOnlyIds, noLocalBytesIds) => {
104573
+ const resolved = new Set;
104574
+ for (const owner of [...data.tasks, ...data.projects]) {
104575
+ for (const attachment of owner.attachments ?? []) {
104576
+ if (attachment.kind !== "file")
104577
+ continue;
104578
+ if (owner.deletedAt || attachment.deletedAt) {
104579
+ if (expectedIds.has(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);
104584
+ }
104585
+ continue;
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
+ }
104592
+ if (!attachment.cloudKey || attachment.localStatus !== "available" || attachment.pendingContentUpload === true) {
104593
+ throw new Error(`Candidate attachment proof failed for ${attachment.id}`);
104594
+ }
104595
+ if (expectedIds.has(attachment.id))
104596
+ resolved.add(attachment.id);
104597
+ }
104598
+ }
104599
+ if (resolved.size !== expectedIds.size) {
104600
+ throw new Error(`Candidate attachment proof incomplete: expected ${expectedIds.size}, proved ${resolved.size}`);
103328
104601
  }
103329
104602
  }, runSharedSyncCycle = async (ports) => {
103330
104603
  return new SharedSyncRunMachine(ports).run();
@@ -103988,8 +105261,19 @@ var DROPBOX_SYNC_PATH = "/data.json", DOWNLOAD_ENDPOINT = "https://content.dropb
103988
105261
  throw new Error(`Dropbox download failed: HTTP ${response.status}`);
103989
105262
  }
103990
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
+ };
103991
105274
  };
103992
105275
  var init_dropbox = __esm(() => {
105276
+ init_attachment_paths();
103993
105277
  init_dropbox_sync_utils();
103994
105278
  init_http_utils();
103995
105279
  init_sync_encryption();
@@ -104013,6 +105297,7 @@ var init_dropbox = __esm(() => {
104013
105297
  this.name = "DropboxFileNotFoundError";
104014
105298
  }
104015
105299
  };
105300
+ DROPBOX_ATTACHMENTS_PATH = `/${ATTACHMENTS_DIR_NAME}`;
104016
105301
  });
104017
105302
 
104018
105303
  // ../../packages/core/src/sync-backend-io.ts
@@ -104056,6 +105341,28 @@ function createSyncBackendIO(ctx, transport) {
104056
105341
  },
104057
105342
  getSyncUrl: () => ctx.syncUrl,
104058
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
+ },
104059
105366
  readRemote: async () => {
104060
105367
  if (ctx.backend === "cloudkit") {
104061
105368
  return transport.cloudKitRead();
@@ -104069,6 +105376,9 @@ function createSyncBackendIO(ctx, transport) {
104069
105376
  const remote2 = await transport.webdavGet();
104070
105377
  webdavDocumentVersion = { exists: remote2.exists, strongEtag: remote2.strongEtag };
104071
105378
  webdavDocumentSnapshot = snapshotWebdavRead(remote2);
105379
+ if (ctx.syncEncryptionOff && !ctx.allowLegacyWebdavPlaintext && remote2.exists && !remote2.strongEtag) {
105380
+ ctx.allowLegacyWebdavPlaintext = true;
105381
+ }
104072
105382
  return remote2.data;
104073
105383
  } catch (error2) {
104074
105384
  webdavDocumentVersion = getWebdavDocumentVersionFromError(error2);
@@ -104223,7 +105533,7 @@ function createSyncBackendIO(ctx, transport) {
104223
105533
  }
104224
105534
  };
104225
105535
  }
104226
- 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";
104227
105537
  var init_sync_backend_io = __esm(() => {
104228
105538
  init_dropbox();
104229
105539
  init_sync_helpers();
@@ -104276,17 +105586,165 @@ var init_sync_fast_sync = __esm(() => {
104276
105586
  init_sync_helpers();
104277
105587
  });
104278
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
+
104279
105735
  // ../../packages/core/src/sync-remote-fence-providers.ts
104280
105736
  var FENCE_MAX_BYTES = 4096, webdavMutationFenceUrl = (documentUrl) => {
104281
- const parsed = new URL(documentUrl);
104282
- const slash = parsed.pathname.lastIndexOf("/");
104283
- parsed.pathname = `${parsed.pathname.slice(0, slash + 1)}${SYNC_REMOTE_MUTATION_FENCE_NAME}`;
104284
- parsed.search = "";
104285
- parsed.hash = "";
104286
- 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}`;
104287
105745
  }, createWebdavSyncRemoteMutationFencePort = (documentUrl, options = {}) => {
104288
105746
  const url = webdavMutationFenceUrl(documentUrl);
104289
- const readOptions = { ...options, maxBytes: FENCE_MAX_BYTES };
105747
+ const readOptions = { ...options, maxBytes: FENCE_MAX_BYTES, treatOversizeAsAbsent: true };
104290
105748
  return {
104291
105749
  read: () => webdavGetFileVersionedWithServerTime(url, readOptions),
104292
105750
  write: (bytes, expectedVersion) => webdavPutFileVersioned(url, bytes, "application/json", expectedVersion, options),
@@ -105928,6 +107386,15 @@ function taskDraftToUpdatePatch(draft, task, options = {}) {
105928
107386
  return null;
105929
107387
  const resolvedProjectId = draft.projectId || undefined;
105930
107388
  const recurrenceValue = draft.recurrence ? { rule: draft.recurrence, strategy: draft.recurrenceStrategy } : undefined;
107389
+ const storedRecurrence = task.recurrence && typeof task.recurrence === "object" ? task.recurrence : undefined;
107390
+ if (recurrenceValue && storedRecurrence?.rule === recurrenceValue.rule) {
107391
+ for (const field of RECURRENCE_ANCHOR_FIELDS) {
107392
+ const value = storedRecurrence[field];
107393
+ if (typeof value === "number") {
107394
+ recurrenceValue[field] = value;
107395
+ }
107396
+ }
107397
+ }
105931
107398
  if (recurrenceValue && draft.recurrenceRRule) {
105932
107399
  const parsed = parseRRuleString(draft.recurrenceRRule);
105933
107400
  if (parsed.byDay && parsed.byDay.length > 0) {
@@ -105936,6 +107403,9 @@ function taskDraftToUpdatePatch(draft, task, options = {}) {
105936
107403
  if (parsed.byMonthDay && parsed.byMonthDay.length > 0) {
105937
107404
  recurrenceValue.byMonthDay = parsed.byMonthDay;
105938
107405
  }
107406
+ if (parsed.weekStart) {
107407
+ recurrenceValue.weekStart = parsed.weekStart;
107408
+ }
105939
107409
  if (parsed.count) {
105940
107410
  recurrenceValue.count = parsed.count;
105941
107411
  }
@@ -105979,8 +107449,32 @@ function taskDraftToUpdatePatch(draft, task, options = {}) {
105979
107449
  ...attachmentsPatch
105980
107450
  };
105981
107451
  }
107452
+ function taskDraftToChangedUpdatePatch(draft, baselineTask, options = {}) {
107453
+ const patch = taskDraftToUpdatePatch(draft, baselineTask, options);
107454
+ if (!patch)
107455
+ return null;
107456
+ const baseline = taskDraftToUpdatePatch(createTaskDraft(baselineTask), baselineTask, {
107457
+ attachments: baselineTask.attachments
107458
+ }) ?? {};
107459
+ const narrowed = { ...patch };
107460
+ for (const key of Object.keys(narrowed)) {
107461
+ const baselineValue = RAW_CONTAINER_TASK_FIELDS.has(key) ? baselineTask[key] : baseline[key];
107462
+ if (areSerializedTaskFieldValuesEqual(narrowed[key], baselineValue)) {
107463
+ delete narrowed[key];
107464
+ }
107465
+ }
107466
+ return narrowed;
107467
+ }
105982
107468
  var trimmedDiffers = (draftValue, taskValue) => draftValue.trim() !== taskValue.trim(), getTaskDraftRecurrenceRRuleValue, TASK_DRAFT_FIELDS, TASK_DRAFT_FIELD_KEYS, attachmentFingerprint = (attachments) => (attachments ?? []).map((attachment) => `${attachment.id}\x00${attachment.uri ?? ""}\x00${attachment.title ?? ""}\x00${attachment.deletedAt ?? ""}`).join(`
105983
- `);
107469
+ `), RECURRENCE_ANCHOR_FIELDS, RAW_CONTAINER_TASK_FIELDS, areSerializedTaskFieldValuesEqual = (left, right) => {
107470
+ if (left === "" && right == null || right === "" && left == null) {
107471
+ return true;
107472
+ }
107473
+ if (Array.isArray(left) || Array.isArray(right) || typeof left === "object" && left !== null || typeof right === "object" && right !== null) {
107474
+ return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
107475
+ }
107476
+ return (left ?? null) === (right ?? null);
107477
+ };
105984
107478
  var init_task_draft = __esm(() => {
105985
107479
  init_bulk_task_tokens();
105986
107480
  init_date();
@@ -106051,6 +107545,13 @@ var init_task_draft = __esm(() => {
106051
107545
  }
106052
107546
  };
106053
107547
  TASK_DRAFT_FIELD_KEYS = Object.keys(TASK_DRAFT_FIELDS);
107548
+ RECURRENCE_ANCHOR_FIELDS = [
107549
+ "anchorDay",
107550
+ "startAnchorDay",
107551
+ "dueAnchorDay",
107552
+ "reviewAnchorDay"
107553
+ ];
107554
+ RAW_CONTAINER_TASK_FIELDS = new Set(["projectId", "sectionId", "areaId"]);
106054
107555
  });
106055
107556
 
106056
107557
  // ../../packages/core/src/contexts.ts
@@ -106790,7 +108291,7 @@ async function undoTaskCompletion(taskId, previousStatus, wasFocusedToday, optio
106790
108291
  return;
106791
108292
  const current = useTaskStore.getState();
106792
108293
  const focusTaskLimit = normalizeFocusTaskLimit(current.settings.gtd?.focusTaskLimit);
106793
- if (current.getDerivedState().focusedCount >= focusTaskLimit)
108294
+ if (current.getFocusedCount() >= focusTaskLimit)
106794
108295
  return;
106795
108296
  const focusResult = await Promise.resolve(current.updateTask(taskId, {
106796
108297
  isFocusedToday: true,
@@ -108070,6 +109571,30 @@ async function cloudGetFile(url, options = {}) {
108070
109571
  return await readResponseBody(res, options.onProgress, options.maxBytes ?? MAX_DOWNLOAD_BYTES, signal);
108071
109572
  });
108072
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
+ }
108073
109598
  async function cloudDeleteFile(url, options = {}) {
108074
109599
  assertCloudUrl(url, options);
108075
109600
  const fetcher = options.fetcher ?? fetch;
@@ -108152,6 +109677,35 @@ var CLOUDKIT_ATTACHMENT_RECORD_TYPE = "MindwtrAttachment", CLOUDKIT_ATTACHMENT_A
108152
109677
  return recordName || null;
108153
109678
  };
108154
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
+
108155
109709
  // ../../packages/core/src/attachment-draft-settlement.ts
108156
109710
  function planAttachmentDraftSettlement({
108157
109711
  baselineAttachments = [],
@@ -110369,7 +111923,8 @@ CREATE TABLE IF NOT EXISTS projects (
110369
111923
  createdAt TEXT NOT NULL,
110370
111924
  updatedAt TEXT NOT NULL,
110371
111925
  deletedAt TEXT,
110372
- purgedAt TEXT
111926
+ purgedAt TEXT,
111927
+ startDate TEXT
110373
111928
  );
110374
111929
 
110375
111930
  CREATE TABLE IF NOT EXISTS areas (
@@ -110776,7 +112331,7 @@ var toJson = (value) => value === undefined ? null : JSON.stringify(value), from
110776
112331
  });
110777
112332
  return fallback;
110778
112333
  }
110779
- }, 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) => {
110780
112335
  if (value === null)
110781
112336
  return null;
110782
112337
  if (value === undefined)
@@ -110906,7 +112461,7 @@ var schema, TASK_SYNC_SCHEMA_VERSION, TASK_SYNC_SCHEMA_VERSION_POLICY, TASK_SYNC
110906
112461
  relativeStartOffset: fromJson(row.relativeStartOffset, undefined),
110907
112462
  dueDate: fromOptional(row.dueDate),
110908
112463
  recurrence: fromJson(row.recurrence, null),
110909
- showFutureRecurrence: fromBool(row.showFutureRecurrence),
112464
+ showFutureRecurrence: fromPresentBool(row.showFutureRecurrence),
110910
112465
  pushCount: row.pushCount === null || row.pushCount === undefined ? undefined : Number(row.pushCount),
110911
112466
  repeatReminderMinutes: row.repeatReminderMinutes === null || row.repeatReminderMinutes === undefined ? undefined : Number(row.repeatReminderMinutes),
110912
112467
  tags: toStringArray(fromJson(row.tags, [])),
@@ -110982,7 +112537,8 @@ var init_project_sync_schema_fixture = __esm(() => {
110982
112537
  { name: "createdAt", nullability: "required", cloudSynced: true, cloudWrite: "managed", sqliteColumn: "createdAt", sqliteOrder: 18, sqliteType: "TEXT" },
110983
112538
  { name: "updatedAt", nullability: "required", cloudSynced: true, cloudWrite: "managed", sqliteColumn: "updatedAt", sqliteOrder: 19, sqliteType: "TEXT" },
110984
112539
  { name: "deletedAt", nullability: "optional", cloudSynced: true, cloudWrite: "patch", sqliteColumn: "deletedAt", sqliteOrder: 20, sqliteType: "TEXT" },
110985
- { 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" }
110986
112542
  ],
110987
112543
  fixture: {
110988
112544
  id: "project-schema-fixture",
@@ -111013,7 +112569,8 @@ var init_project_sync_schema_fixture = __esm(() => {
111013
112569
  createdAt: "2026-07-01T10:00:00.000Z",
111014
112570
  updatedAt: "2026-07-14T11:00:00.000Z",
111015
112571
  deletedAt: "2026-07-14T11:30:00.000Z",
111016
- purgedAt: "2026-07-14T11:45:00.000Z"
112572
+ purgedAt: "2026-07-14T11:45:00.000Z",
112573
+ startDate: "2026-07-10T09:00:00.000Z"
111017
112574
  }
111018
112575
  };
111019
112576
  });
@@ -111044,6 +112601,7 @@ var schema2, PROJECT_SYNC_SCHEMA_VERSION, PROJECT_SYNC_FIELD_SCHEMA, PROJECT_SYN
111044
112601
  supportNotes: project.supportNotes ?? null,
111045
112602
  attachments: toJson(project.attachments),
111046
112603
  dueDate: project.dueDate ?? null,
112604
+ startDate: project.startDate ?? null,
111047
112605
  reviewAt: project.reviewAt ?? null,
111048
112606
  areaId: project.areaId ?? null,
111049
112607
  areaTitle: project.areaTitle ?? null,
@@ -111070,6 +112628,7 @@ var schema2, PROJECT_SYNC_SCHEMA_VERSION, PROJECT_SYNC_FIELD_SCHEMA, PROJECT_SYN
111070
112628
  supportNotes: fromOptional(row.supportNotes),
111071
112629
  attachments: toAttachments(fromJson(row.attachments, undefined)),
111072
112630
  dueDate: fromOptional(row.dueDate),
112631
+ startDate: fromOptional(row.startDate),
111073
112632
  reviewAt: fromOptional(row.reviewAt),
111074
112633
  areaId: fromOptional(row.areaId),
111075
112634
  areaTitle: fromOptional(row.areaTitle),
@@ -113232,7 +114791,37 @@ var BACKUP_FILE_PREFIX = "mindwtr-backup-", MAX_BACKUP_SOURCE_BYTES, MAX_BACKUP_
113232
114791
  delete nextSettings.security;
113233
114792
  }
113234
114793
  return nextSettings;
113235
- }, sanitizeRestoredAttachments = (item) => item.attachments ? { ...item, attachments: normalizeAttachmentsForSyncMerge(item.attachments) } : item, prepareRestoredBackupDataForSync = (data, options = {}) => {
114794
+ }, sanitizeRestoredAttachments = (item) => item.attachments ? { ...item, attachments: normalizeAttachmentsForSyncMerge(item.attachments) } : item, prepareRestoredAttachmentsForSync = (item, restoredAt, previous) => {
114795
+ const restoredAttachments = normalizeAttachmentsForSyncMerge(item.attachments) ?? [];
114796
+ const previousAttachments = normalizeAttachmentsForSyncMerge(previous?.attachments) ?? [];
114797
+ if (restoredAttachments.length === 0 && previousAttachments.length === 0) {
114798
+ return sanitizeRestoredAttachments(item);
114799
+ }
114800
+ const restoredIds = new Set(restoredAttachments.map((attachment) => attachment.id));
114801
+ const previousAttachmentsById = new Map(previousAttachments.map((attachment) => [attachment.id, attachment]));
114802
+ const refreshed = restoredAttachments.map((attachment) => {
114803
+ const previousAttachment = previousAttachmentsById.get(attachment.id);
114804
+ const shouldPublishRestoredBytes = attachment.kind === "file" && !item.deletedAt && !attachment.deletedAt;
114805
+ return {
114806
+ ...attachment,
114807
+ ...attachment.kind === "file" ? {
114808
+ contentRev: nextRevision(Math.max(normalizeRevision(attachment.contentRev), normalizeRevision(previousAttachment?.contentRev)))
114809
+ } : {},
114810
+ ...shouldPublishRestoredBytes ? { pendingContentUpload: true } : {},
114811
+ ...attachment.deletedAt ? { deletedAt: restoredAt } : {},
114812
+ updatedAt: restoredAt
114813
+ };
114814
+ });
114815
+ const carriedTombstones = previousAttachments.filter((attachment) => !restoredIds.has(attachment.id)).map((attachment) => ({
114816
+ ...attachment,
114817
+ deletedAt: restoredAt,
114818
+ updatedAt: restoredAt
114819
+ }));
114820
+ return {
114821
+ ...item,
114822
+ attachments: [...refreshed, ...carriedTombstones]
114823
+ };
114824
+ }, prepareRestoredBackupDataForSync = (data, options = {}) => {
113236
114825
  const restoredAt = toIsoString(options.restoredAt) ?? new Date().toISOString();
113237
114826
  const restoredSettings = stripDeviceLocalRestoreSettings(data.settings);
113238
114827
  const previous = options.previousData ?? null;
@@ -113245,11 +114834,11 @@ var BACKUP_FILE_PREFIX = "mindwtr-backup-", MAX_BACKUP_SOURCE_BYTES, MAX_BACKUP_
113245
114834
  const tasks = prepare(data.tasks, previous?.tasks).map((task) => task.purgedAt ? compactPurgedTaskForLocalStorage({
113246
114835
  ...task,
113247
114836
  attachments: previousTasksById.get(task.id)?.attachments
113248
- }) : sanitizeRestoredAttachments(task));
114837
+ }) : prepareRestoredAttachmentsForSync(task, restoredAt, previousTasksById.get(task.id)));
113249
114838
  const projects = prepare(data.projects, previous?.projects).map((project) => project.purgedAt ? compactPurgedProjectForLocalStorage({
113250
114839
  ...project,
113251
114840
  attachments: previousProjectsById.get(project.id)?.attachments
113252
- }) : sanitizeRestoredAttachments(project));
114841
+ }) : prepareRestoredAttachmentsForSync(project, restoredAt, previousProjectsById.get(project.id)));
113253
114842
  const sections = compactSectionsForPurgedProjects(prepare(data.sections, previous?.sections), projects);
113254
114843
  return {
113255
114844
  ...data,
@@ -115180,6 +116769,7 @@ function applyImport(currentData, parsed, opts) {
115180
116769
  tagIds: project.tagIds ?? [],
115181
116770
  supportNotes: project.supportNotes,
115182
116771
  dueDate: project.dueDate,
116772
+ startDate: project.startDate,
115183
116773
  createdAt,
115184
116774
  updatedAt,
115185
116775
  rev: nextRevision(),
@@ -116190,6 +117780,7 @@ var normalizeFolders = (rawFolders) => {
116190
117780
  areaSourceId: record3.folderId,
116191
117781
  color: record3.color,
116192
117782
  dueDate: record3.dueDate,
117783
+ startDate: record3.startDate,
116193
117784
  supportNotes,
116194
117785
  isArchived: Boolean(record3.completedAt),
116195
117786
  createdAt: record3.createdAt,
@@ -116567,6 +118158,7 @@ ${next}`;
116567
118158
  const project2 = ensureProjectRecord(projectsByKey, row.name, allocateProjectOrder);
116568
118159
  project2.status = parseProjectStatus(row.statusText || "");
116569
118160
  project2.dueDate = dueMapping.value ?? project2.dueDate;
118161
+ project2.startDate = startMapping.value ?? project2.startDate;
116570
118162
  project2.supportNotes = mergeProjectSupportNotes(project2.supportNotes, joinDescription([
116571
118163
  row.notes,
116572
118164
  plannedMapping.value ? `Planned date in OmniFocus: ${plannedMapping.value}` : undefined,
@@ -116958,6 +118550,7 @@ ${next}`;
116958
118550
  areaSourceKey: ensureAreaRecord(project.folderId, project.folderName),
116959
118551
  status: project.completed || rootTask?.completed ? "archived" : parseProjectStatus(project.statusText || rootTask?.statusText || ""),
116960
118552
  dueDate: dateNotes.dueDate,
118553
+ startDate: dateNotes.startTime,
116961
118554
  supportNotes: joinDescription([
116962
118555
  project.note,
116963
118556
  rootTask?.note,
@@ -116988,6 +118581,7 @@ ${next}`;
116988
118581
  order: projects.length,
116989
118582
  status: task.completed ? "archived" : parseProjectStatus(task.statusText || ""),
116990
118583
  dueDate: dateNotes.dueDate,
118584
+ startDate: dateNotes.startTime,
116991
118585
  supportNotes: joinDescription([
116992
118586
  task.note,
116993
118587
  dateNotes.plannedNote,
@@ -118527,6 +120121,42 @@ var init_capture = __esm(() => {
118527
120121
  init_color_constants();
118528
120122
  });
118529
120123
 
120124
+ // ../../packages/core/src/capture-session.ts
120125
+ class CaptureSessionCoordinator {
120126
+ currentSession = 0;
120127
+ submittingSession = null;
120128
+ beginSession() {
120129
+ this.currentSession += 1;
120130
+ this.submittingSession = null;
120131
+ return this.currentSession;
120132
+ }
120133
+ invalidateSession(session) {
120134
+ if (!this.isCurrent(session))
120135
+ return;
120136
+ this.currentSession += 1;
120137
+ this.submittingSession = null;
120138
+ }
120139
+ isCurrent(session) {
120140
+ return session === this.currentSession;
120141
+ }
120142
+ isSubmitting(session) {
120143
+ return this.isCurrent(session) && this.submittingSession === session;
120144
+ }
120145
+ tryBeginSubmission(session) {
120146
+ if (!this.isCurrent(session) || this.submittingSession !== null)
120147
+ return false;
120148
+ this.submittingSession = session;
120149
+ return true;
120150
+ }
120151
+ finishSubmission(session) {
120152
+ if (!this.isCurrent(session))
120153
+ return false;
120154
+ if (this.submittingSession === session)
120155
+ this.submittingSession = null;
120156
+ return true;
120157
+ }
120158
+ }
120159
+
118530
120160
  // ../../packages/core/src/session-restore.ts
118531
120161
  function shouldRestoreLastView(savedAtMs, nowMs = Date.now()) {
118532
120162
  if (typeof savedAtMs !== "number" || !Number.isFinite(savedAtMs))
@@ -118770,6 +120400,39 @@ var init_import_runner = __esm(() => {
118770
120400
  });
118771
120401
 
118772
120402
  // ../../packages/core/src/global-search-filter.ts
120403
+ function getGlobalSearchFilterPresentation(t) {
120404
+ const translated = (key, fallback) => {
120405
+ const value = t(key);
120406
+ return value && value !== key ? value : fallback;
120407
+ };
120408
+ const scope = {
120409
+ all: translated("search.scope.all", "All"),
120410
+ projects: translated("search.scope.projects", "Projects only"),
120411
+ tasks: translated("search.scope.tasks", "Tasks only"),
120412
+ project_tasks: translated("search.scope.projectTasks", "Tasks in projects")
120413
+ };
120414
+ const due = {
120415
+ any: translated("search.due.any", "Any"),
120416
+ overdue: translated("search.due.overdue", "Overdue"),
120417
+ today: translated("search.due.today", "Today"),
120418
+ tomorrow: translated("search.due.tomorrow", "Tomorrow"),
120419
+ this_week: translated("search.due.thisWeek", "This week"),
120420
+ next_week: translated("search.due.nextWeek", "Next week"),
120421
+ none: translated("search.due.none", "No due date")
120422
+ };
120423
+ return {
120424
+ sections: {
120425
+ status: translated("taskEdit.statusLabel", "Status"),
120426
+ scope: translated("search.scope.label", "Scope"),
120427
+ area: translated("taskEdit.areaLabel", "Area"),
120428
+ due: translated("search.due.label", "Due date"),
120429
+ tokens: translated("filters.contexts", "Contexts & tags")
120430
+ },
120431
+ scope,
120432
+ due,
120433
+ clear: translated("filters.clear", "Clear")
120434
+ };
120435
+ }
118773
120436
  var buildDueMatcher = (duePreset, weekStart) => {
118774
120437
  const now3 = new Date;
118775
120438
  const startOfToday3 = new Date(now3.getFullYear(), now3.getMonth(), now3.getDate());
@@ -119581,6 +121244,7 @@ var init_settings_search_keys = __esm(() => {
119581
121244
  { key: "density", section: "lookAndFeel" },
119582
121245
  { key: "textSize", section: "lookAndFeel" },
119583
121246
  { key: "showTaskAge", section: "lookAndFeel" },
121247
+ { key: "sidebarViews", section: "lookAndFeel" },
119584
121248
  { key: "language", section: "localization" },
119585
121249
  { key: "weekStart", section: "localization" },
119586
121250
  { key: "dateFormat", section: "localization" },
@@ -119601,6 +121265,7 @@ var init_settings_search_keys = __esm(() => {
119601
121265
  "focusTaskLimit",
119602
121266
  "defaultProjectFlowMode",
119603
121267
  "features",
121268
+ { key: "featureTimeline", section: "features" },
119604
121269
  { key: "featurePomodoro", section: "features" },
119605
121270
  { key: "pomodoroCustomPreset", section: "features" },
119606
121271
  { key: "pomodoroLinkTask", section: "features" },
@@ -119736,6 +121401,7 @@ var init_settings_search_keys = __esm(() => {
119736
121401
  "checkForUpdates",
119737
121402
  "feedback",
119738
121403
  "documentation",
121404
+ "videoTutorials",
119739
121405
  "privacy",
119740
121406
  "github",
119741
121407
  "sponsorProject",
@@ -119765,6 +121431,7 @@ var init_settings_search_keys = __esm(() => {
119765
121431
  SETTINGS_SEARCH_PAGE_IDS = Object.keys(SETTINGS_SEARCH_PAGE_KEYS);
119766
121432
  SETTINGS_SEARCH_INDEX = SETTINGS_SEARCH_PAGE_IDS.flatMap((pageId) => getSettingsSearchEntries(pageId));
119767
121433
  SETTINGS_SEARCH_MOBILE_EXCLUSIONS = {
121434
+ featureTimeline: "Timeline view exists on desktop only; the mobile GTD > Features screen has no row for it (#1145).",
119768
121435
  density: "No adjustable list density setting on mobile.",
119769
121436
  textSize: "Mobile follows the OS text-size setting automatically; no in-app override.",
119770
121437
  keybindings: "No hardware-keyboard shortcuts configuration on mobile.",
@@ -119898,8 +121565,14 @@ __export(exports_src, {
119898
121565
  taskMatchesAreaFilterSelection: () => taskMatchesAreaFilterSelection,
119899
121566
  taskFromSqliteRow: () => taskFromSqliteRow,
119900
121567
  taskDraftToUpdatePatch: () => taskDraftToUpdatePatch,
121568
+ taskDraftToChangedUpdatePatch: () => taskDraftToChangedUpdatePatch,
119901
121569
  tFallback: () => tFallback,
119902
121570
  syncPlaintextArtifactName: () => syncPlaintextArtifactName,
121571
+ syncEncryptionScopeLabel: () => syncEncryptionScopeLabel,
121572
+ syncEncryptionSaltPrefix: () => syncEncryptionSaltPrefix,
121573
+ syncEncryptionLogMessage: () => syncEncryptionLogMessage,
121574
+ syncEncryptionKdfLabel: () => syncEncryptionKdfLabel,
121575
+ syncEncryptionArtifactLabel: () => syncEncryptionArtifactLabel,
119903
121576
  syncEncryptedArtifactName: () => syncEncryptedArtifactName,
119904
121577
  summarizeTaskLifecycleCounts: () => summarizeTaskLifecycleCounts,
119905
121578
  summarizeMergeStats: () => summarizeMergeStats,
@@ -119939,6 +121612,7 @@ __export(exports_src, {
119939
121612
  setTaskViewSectionId: () => setTaskViewSectionId,
119940
121613
  setTaskDraftField: () => setTaskDraftField,
119941
121614
  setStorageAdapter: () => setStorageAdapter,
121615
+ setSleepBypass: () => setSleepBypass,
119942
121616
  setSha256HexProvider: () => setSha256HexProvider,
119943
121617
  setLogger: () => setLogger,
119944
121618
  setComposerTitle: () => setComposerTitle,
@@ -119965,6 +121639,7 @@ __export(exports_src, {
119965
121639
  selectVisiblePeople: () => selectVisiblePeople,
119966
121640
  selectVisibleAreas: () => selectVisibleAreas,
119967
121641
  selectProcessInboxCandidates: () => selectProcessInboxCandidates,
121642
+ selectFocusedCount: () => selectFocusedCount,
119968
121643
  selectComposerTask: () => selectComposerTask,
119969
121644
  sectionToSqliteRow: () => sectionToSqliteRow,
119970
121645
  sectionFromSqliteRow: () => sectionFromSqliteRow,
@@ -120022,6 +121697,9 @@ __export(exports_src, {
120022
121697
  resolveTextDirection: () => resolveTextDirection,
120023
121698
  resolveTaskViewSection: () => resolveTaskViewSection,
120024
121699
  resolveTaskTextDirection: () => resolveTaskTextDirection,
121700
+ resolveTaskSortByForFeatures: () => resolveTaskSortByForFeatures,
121701
+ resolveTaskPerspectiveForFeatures: () => resolveTaskPerspectiveForFeatures,
121702
+ resolveTaskGroupByForFeatures: () => resolveTaskGroupByForFeatures,
120025
121703
  resolveSyncFailureCooldownMs: () => resolveSyncFailureCooldownMs,
120026
121704
  resolveSyncBackend: () => resolveSyncBackend,
120027
121705
  resolveSettingsSearchI18nKey: () => resolveSettingsSearchI18nKey,
@@ -120050,6 +121728,7 @@ __export(exports_src, {
120050
121728
  resolveAnthropicModel: () => resolveAnthropicModel,
120051
121729
  resetUnhashableAttachmentStatsForTests: () => resetUnhashableAttachmentStatsForTests,
120052
121730
  resetPomodoroState: () => resetPomodoroState,
121731
+ resetLastSyncEncryptionError: () => resetLastSyncEncryptionError,
120053
121732
  resetHeartbeatOptOutMarker: () => resetHeartbeatOptOutMarker,
120054
121733
  resetForTests: () => resetForTests,
120055
121734
  rescheduleTask: () => rescheduleTask,
@@ -120057,6 +121736,7 @@ __export(exports_src, {
120057
121736
  replaceEntityInMap: () => replaceEntityInMap,
120058
121737
  replaceEntityInArray: () => replaceEntityInArray,
120059
121738
  replaceEntitiesInArray: () => replaceEntitiesInArray,
121739
+ repairMissingRemoteAttachments: () => repairMissingRemoteAttachments,
120060
121740
  repairMergedSyncReferences: () => repairMergedSyncReferences,
120061
121741
  removeAdvancedFilterCriteriaChip: () => removeAdvancedFilterCriteriaChip,
120062
121742
  recordUpdateReminderShown: () => recordUpdateReminderShown,
@@ -120252,6 +121932,7 @@ __export(exports_src, {
120252
121932
  isSyncFilePath: () => isSyncFilePath,
120253
121933
  isSyncFileLockUnavailableError: () => isSyncFileLockUnavailableError,
120254
121934
  isSyncFileGenerationCorruptError: () => isSyncFileGenerationCorruptError,
121935
+ isSyncEncryptionStateBlocked: () => isSyncEncryptionStateBlocked,
120255
121936
  isSyncEncryptionRemoteVersionUnavailableError: () => isSyncEncryptionRemoteVersionUnavailableError,
120256
121937
  isSupportedLanguage: () => isSupportedLanguage,
120257
121938
  isSlotFreeForDay: () => isSlotFreeForDay,
@@ -120281,6 +121962,7 @@ __export(exports_src, {
120281
121962
  isMindwtrMirrorEvent: () => isMindwtrMirrorEvent,
120282
121963
  isMindwtrMirrorCalendar: () => isMindwtrMirrorCalendar,
120283
121964
  isMarkdownEditorAssistEnabled: () => isMarkdownEditorAssistEnabled,
121965
+ isLocalPersistEquivalent: () => isLocalPersistEquivalent,
120284
121966
  isLikelyOfflineSyncError: () => isLikelyOfflineSyncError,
120285
121967
  isJalaliCalendarLocale: () => isJalaliCalendarLocale,
120286
121968
  isFocusSequentialCandidate: () => isFocusSequentialCandidate,
@@ -120292,12 +121974,14 @@ __export(exports_src, {
120292
121974
  isDropboxPathNotFoundTag: () => isDropboxPathNotFoundTag,
120293
121975
  isDropboxPathConflictTag: () => isDropboxPathConflictTag,
120294
121976
  isDropboxConflictError: () => isDropboxConflictError,
121977
+ isDeepJsonEqual: () => isDeepJsonEqual,
120295
121978
  isCustomTimeEstimate: () => isCustomTimeEstimate,
120296
121979
  isConnectionAllowed: () => isConnectionAllowed,
120297
121980
  isCompletedCalendarTask: () => isCompletedCalendarTask,
120298
121981
  isCalendarFeedTask: () => isCalendarFeedTask,
120299
121982
  isAttachmentUploadTooLargeError: () => isAttachmentUploadTooLargeError,
120300
121983
  isAttachmentUploadAdmissionError: () => isAttachmentUploadAdmissionError,
121984
+ isAttachmentPresenceRepairCandidate: () => isAttachmentPresenceRepairCandidate,
120301
121985
  isAttachmentLocalResourceReferenced: () => isAttachmentLocalResourceReferenced,
120302
121986
  isAttachmentCloudResourceReferenced: () => isAttachmentCloudResourceReferenced,
120303
121987
  isAreaFilterSelectionActive: () => isAreaFilterSelectionActive,
@@ -120381,6 +122065,7 @@ __export(exports_src, {
120381
122065
  getProjectsByArea: () => getProjectsByArea,
120382
122066
  getProjectedRecurringTaskId: () => getProjectedRecurringTaskId,
120383
122067
  getProjectedRecurringTaskCalendarDate: () => getProjectedRecurringTaskCalendarDate,
122068
+ getProjectSectionsForView: () => getProjectSectionsForView,
120384
122069
  getProjectReviewReminderIntent: () => getProjectReviewReminderIntent,
120385
122070
  getProjectNextActionState: () => getProjectNextActionState,
120386
122071
  getProjectNextActionPromptData: () => getProjectNextActionPromptData,
@@ -120408,10 +122093,12 @@ __export(exports_src, {
120408
122093
  getLocalizedWeekdayLabel: () => getLocalizedWeekdayLabel,
120409
122094
  getLocalizedWeekdayButtons: () => getLocalizedWeekdayButtons,
120410
122095
  getLocaleCoverageTier: () => getLocaleCoverageTier,
122096
+ getLastSyncEncryptionError: () => getLastSyncEncryptionError,
120411
122097
  getInlineMarkdownPreview: () => getInlineMarkdownPreview,
120412
122098
  getInMemorySyncChangeFingerprint: () => getInMemorySyncChangeFingerprint,
120413
122099
  getInMemoryAppDataSnapshot: () => getInMemoryAppDataSnapshot,
120414
122100
  getI18nKeyForEnglishText: () => getI18nKeyForEnglishText,
122101
+ getGlobalSearchFilterPresentation: () => getGlobalSearchFilterPresentation,
120415
122102
  getFrequentTaskTokensFromUsage: () => getFrequentTaskTokensFromUsage,
120416
122103
  getFrequentTaskTokens: () => getFrequentTaskTokens,
120417
122104
  getFocusStarBlockedText: () => getFocusStarBlockedText,
@@ -120460,9 +122147,11 @@ __export(exports_src, {
120460
122147
  formatTaskMovedMessage: () => formatTaskMovedMessage,
120461
122148
  formatTaskMarkedDoneMessage: () => formatTaskMarkedDoneMessage,
120462
122149
  formatSyncErrorMessage: () => formatSyncErrorMessage,
122150
+ formatSyncEncryptionDiagnostics: () => formatSyncEncryptionDiagnostics,
120463
122151
  formatSettingsSearchPath: () => formatSettingsSearchPath,
120464
122152
  formatRecurrenceLabel: () => formatRecurrenceLabel,
120465
122153
  formatRecurrenceCountLabel: () => formatRecurrenceCountLabel,
122154
+ formatQuickAddHelp: () => formatQuickAddHelp,
120466
122155
  formatPomodoroClock: () => formatPomodoroClock,
120467
122156
  formatOpenAIExtraBodyParams: () => formatOpenAIExtraBodyParams,
120468
122157
  formatLocalDateTime: () => formatLocalDateTime2,
@@ -120475,6 +122164,7 @@ __export(exports_src, {
120475
122164
  formatCalendarDurationLabel: () => formatCalendarDurationLabel,
120476
122165
  formatAIErrorAlertBody: () => formatAIErrorAlertBody,
120477
122166
  flushPendingSave: () => flushPendingSave,
122167
+ findSyncEncryptionSentinel: () => findSyncEncryptionSentinel,
120478
122168
  findSelectableProjectByTitleAndArea: () => findSelectableProjectByTitleAndArea,
120479
122169
  findPendingAttachmentUploads: () => findPendingAttachmentUploads,
120480
122170
  findOrphanedAttachments: () => findOrphanedAttachments,
@@ -120510,6 +122200,7 @@ __export(exports_src, {
120510
122200
  ensureDeviceId: () => ensureDeviceId,
120511
122201
  endOfCalendarMonth: () => endOfCalendarMonth,
120512
122202
  encryptSyncArtifact: () => encryptSyncArtifact,
122203
+ editRRuleString: () => editRRuleString,
120513
122204
  downloadDropboxFileVersionedWithServerTime: () => downloadDropboxFileVersionedWithServerTime,
120514
122205
  downloadDropboxFileVersioned: () => downloadDropboxFileVersioned,
120515
122206
  downloadDropboxFile: () => downloadDropboxFile,
@@ -120551,6 +122242,7 @@ __export(exports_src, {
120551
122242
  createImportDiagnostic: () => createImportDiagnostic,
120552
122243
  createImportArchiveBudget: () => createImportArchiveBudget,
120553
122244
  createDropboxSyncRemoteMutationFencePort: () => createDropboxSyncRemoteMutationFencePort,
122245
+ createDropboxAttachmentPresenceIndex: () => createDropboxAttachmentPresenceIndex,
120554
122246
  createDefaultSyncRunStoreBridge: () => createDefaultSyncRunStoreBridge,
120555
122247
  createCustomTimeEstimate: () => createCustomTimeEstimate,
120556
122248
  createCurrentRecurringCalendarTask: () => createCurrentRecurringCalendarTask,
@@ -120604,9 +122296,11 @@ __export(exports_src, {
120604
122296
  cloudGetJson: () => cloudGetJson,
120605
122297
  cloudGetFile: () => cloudGetFile,
120606
122298
  cloudDeleteFile: () => cloudDeleteFile,
122299
+ cloudAttachmentExists: () => cloudAttachmentExists,
120607
122300
  cloneSettings: () => cloneSettings,
120608
122301
  cloneAppData: () => cloneAppData,
120609
122302
  clearProviderModelsCache: () => clearProviderModelsCache,
122303
+ clearIdleSyncCycleSnapshot: () => clearIdleSyncCycleSnapshot,
120610
122304
  clearDeletedTaskProjectArchiveMetadata: () => clearDeletedTaskProjectArchiveMetadata,
120611
122305
  clearBreadcrumbs: () => clearBreadcrumbs,
120612
122306
  checkAttachmentContentChange: () => checkAttachmentContentChange,
@@ -120624,6 +122318,12 @@ __export(exports_src, {
120624
122318
  buildSyncPayloadTraceExtra: () => buildSyncPayloadTraceExtra,
120625
122319
  buildSyncPayloadSurfaceTraceExtra: () => buildSyncPayloadSurfaceTraceExtra,
120626
122320
  buildSyncPayloadDiffTraceExtra: () => buildSyncPayloadDiffTraceExtra,
122321
+ buildSyncLocationScope: () => buildSyncLocationScope,
122322
+ buildSyncEncryptionTransitionExtra: () => buildSyncEncryptionTransitionExtra,
122323
+ buildSyncEncryptionStateExtra: () => buildSyncEncryptionStateExtra,
122324
+ buildSyncEncryptionRemoteReadExtra: () => buildSyncEncryptionRemoteReadExtra,
122325
+ buildSyncEncryptionErrorExtra: () => buildSyncEncryptionErrorExtra,
122326
+ buildSyncEncryptionActivationExtra: () => buildSyncEncryptionActivationExtra,
120627
122327
  buildSpeechTranscriptionPrompt: () => buildSpeechTranscriptionPrompt,
120628
122328
  buildSpeechToTaskPrompt: () => buildSpeechToTaskPrompt,
120629
122329
  buildSettingsSearchResults: () => buildSettingsSearchResults,
@@ -120778,8 +122478,13 @@ __export(exports_src, {
120778
122478
  SYNC_FILE_GENERATION_CORRUPT_CODE: () => SYNC_FILE_GENERATION_CORRUPT_CODE,
120779
122479
  SYNC_ENCRYPTION_TRANSITION_INCOMPLETE: () => SYNC_ENCRYPTION_TRANSITION_INCOMPLETE,
120780
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,
120781
122485
  SYNC_ENCRYPTION_KEYED_STATES: () => SYNC_ENCRYPTION_KEYED_STATES,
120782
122486
  SYNC_CRYPTO_DEFAULT_KDF_PARAMS: () => SYNC_CRYPTO_DEFAULT_KDF_PARAMS,
122487
+ SUSPENDED_REQUEST_MESSAGE: () => SUSPENDED_REQUEST_MESSAGE,
120783
122488
  SUPPORTED_LANGUAGES: () => SUPPORTED_LANGUAGES,
120784
122489
  STORE_REVIEW_MIN_DAYS_SINCE_FIRST_SEEN: () => STORE_REVIEW_MIN_DAYS_SINCE_FIRST_SEEN,
120785
122490
  STORE_REVIEW_MIN_ACTIVE_DAYS: () => STORE_REVIEW_MIN_ACTIVE_DAYS,
@@ -120882,6 +122587,7 @@ __export(exports_src, {
120882
122587
  DropboxFileNotFoundError: () => DropboxFileNotFoundError,
120883
122588
  DropboxConflictError: () => DropboxConflictError,
120884
122589
  DataTransferRefreshError: () => DataTransferRefreshError,
122590
+ DROPBOX_ATTACHMENTS_PATH: () => DROPBOX_ATTACHMENTS_PATH,
120885
122591
  DRACULA_EXTERNAL_CALENDAR_COLOR_MAP: () => DRACULA_EXTERNAL_CALENDAR_COLOR_MAP,
120886
122592
  DRACULA_CONTEXT_COLOR_PALETTE: () => DRACULA_CONTEXT_COLOR_PALETTE,
120887
122593
  DONATION_PROMPT_SUPPORT_CLICK_COOLDOWN_MS: () => DONATION_PROMPT_SUPPORT_CLICK_COOLDOWN_MS,
@@ -120915,6 +122621,7 @@ __export(exports_src, {
120915
122621
  DEFAULT_AREA_COLOR: () => DEFAULT_AREA_COLOR,
120916
122622
  DEFAULT_ANTHROPIC_THINKING_BUDGET: () => DEFAULT_ANTHROPIC_THINKING_BUDGET,
120917
122623
  CloudHttpError: () => CloudHttpError,
122624
+ CaptureSessionCoordinator: () => CaptureSessionCoordinator,
120918
122625
  CUSTOM_TIME_ESTIMATE_PREFIX: () => CUSTOM_TIME_ESTIMATE_PREFIX,
120919
122626
  COPILOT_REASONING_EFFORT: () => COPILOT_REASONING_EFFORT,
120920
122627
  COMPLETION_DATE_GROUPS: () => COMPLETION_DATE_GROUPS,
@@ -120987,6 +122694,7 @@ var init_src = __esm(() => {
120987
122694
  init_sync_fast_sync();
120988
122695
  init_sync_crypto();
120989
122696
  init_sync_encryption();
122697
+ init_sync_encryption_diagnostics();
120990
122698
  init_sync_remote_fence();
120991
122699
  init_sync_remote_fence_providers();
120992
122700
  init_diceware();
@@ -134482,6 +136190,7 @@ var createCloudService = (options) => {
134482
136190
  isSequential: input.isSequential,
134483
136191
  isFocused: input.isFocused,
134484
136192
  dueDate: input.dueDate ?? undefined,
136193
+ startDate: input.startDate ?? undefined,
134485
136194
  reviewAt: input.reviewAt ?? undefined,
134486
136195
  supportNotes: input.supportNotes ?? undefined
134487
136196
  })
@@ -134504,6 +136213,8 @@ var createCloudService = (options) => {
134504
136213
  patch.isFocused = input.isFocused;
134505
136214
  if (input.dueDate !== undefined)
134506
136215
  patch.dueDate = input.dueDate;
136216
+ if (input.startDate !== undefined)
136217
+ patch.startDate = input.startDate;
134507
136218
  if (input.reviewAt !== undefined)
134508
136219
  patch.reviewAt = input.reviewAt;
134509
136220
  if (input.supportNotes !== undefined)
@@ -137011,7 +138722,8 @@ var getProjectColumns = (db) => {
137011
138722
  const names = new Set(columns.map((col) => String(col.name)));
137012
138723
  const hasOrderNum = names.has("orderNum");
137013
138724
  const hasDueDate = names.has("dueDate");
137014
- 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"));
137015
138727
  const resolved = { hasOrderNum, selectColumns };
137016
138728
  projectColumnsCache.set(db, resolved);
137017
138729
  return resolved;
@@ -137433,6 +139145,18 @@ var ensureCoreReady = async (options) => {
137433
139145
  const runWriteTransaction = coreReadonly ? runDirectly : (operation) => withMcpWriteLock(resolvedPath, operation);
137434
139146
  coreService = {
137435
139147
  ...createCorePersistenceService(core2, runWriteTransaction),
139148
+ getQuickAddSnapshot: async () => runWriteTransaction(async () => {
139149
+ const state = core2.useTaskStore.getState();
139150
+ await state.fetchData();
139151
+ const current = core2.useTaskStore.getState();
139152
+ return {
139153
+ tasks: current.tasks,
139154
+ projects: current.projects,
139155
+ areas: current.areas,
139156
+ people: current.people,
139157
+ settings: current.settings
139158
+ };
139159
+ }),
137436
139160
  addProject: async ({ title, color, props }) => runWriteTransaction(async () => {
137437
139161
  const state = core2.useTaskStore.getState();
137438
139162
  await state.fetchData();
@@ -137824,8 +139548,9 @@ var createService = (options, deps = defaultServiceDeps) => {
137824
139548
  const recurrence2 = normalizeOptionalTaskRecurrence(normalizedInput.recurrence);
137825
139549
  return await runCoreWriteWithRetries(options, deps, async (core2) => {
137826
139550
  if (normalizedInput.quickAdd) {
137827
- const projects = await withDb((db) => deps.listProjects(db));
137828
- const quick = deps.parseQuickAdd(normalizedInput.quickAdd, projects);
139551
+ const snapshot = await core2.getQuickAddSnapshot();
139552
+ const { projects, areas, tasks, people: people2, settings } = snapshot;
139553
+ const quick = deps.parseQuickAdd(normalizedInput.quickAdd, projects, undefined, areas, buildQuickAddParseOptions(settings, { tasks, people: people2 }));
137829
139554
  let createdTask;
137830
139555
  const capture2 = await executeCaptureTransaction({
137831
139556
  parsed: {
@@ -137913,6 +139638,7 @@ var createService = (options, deps = defaultServiceDeps) => {
137913
139638
  isSequential: input.isSequential,
137914
139639
  isFocused: input.isFocused,
137915
139640
  dueDate: input.dueDate ?? undefined,
139641
+ startDate: input.startDate ?? undefined,
137916
139642
  reviewAt: input.reviewAt ?? undefined,
137917
139643
  supportNotes: input.supportNotes ?? undefined
137918
139644
  })
@@ -137934,6 +139660,8 @@ var createService = (options, deps = defaultServiceDeps) => {
137934
139660
  updates.isFocused = input.isFocused;
137935
139661
  if (input.dueDate !== undefined)
137936
139662
  updates.dueDate = input.dueDate ?? undefined;
139663
+ if (input.startDate !== undefined)
139664
+ updates.startDate = input.startDate ?? undefined;
137937
139665
  if (input.reviewAt !== undefined)
137938
139666
  updates.reviewAt = input.reviewAt ?? undefined;
137939
139667
  if (input.supportNotes !== undefined)
@@ -138310,6 +140038,7 @@ var addProjectSchema = objectType({
138310
140038
  isSequential: booleanType().optional(),
138311
140039
  isFocused: booleanType().optional(),
138312
140040
  dueDate: isoDateLikeSchema.nullable().optional(),
140041
+ startDate: isoDateLikeSchema.nullable().optional(),
138313
140042
  reviewAt: isoDateLikeSchema.nullable().optional(),
138314
140043
  supportNotes: stringType().nullable().optional()
138315
140044
  });
@@ -138322,6 +140051,7 @@ var updateProjectSchema = objectType({
138322
140051
  isSequential: booleanType().optional(),
138323
140052
  isFocused: booleanType().optional(),
138324
140053
  dueDate: isoDateLikeSchema.nullable().optional(),
140054
+ startDate: isoDateLikeSchema.nullable().optional(),
138325
140055
  reviewAt: isoDateLikeSchema.nullable().optional(),
138326
140056
  supportNotes: stringType().nullable().optional()
138327
140057
  });