Commit Graph
1777 Commits
Author SHA1 Message Date
nihilist-narwhal e294345599 Protection: keep hidden formulas hidden
On a protected sheet a formula formatted Hidden still showed with Show
Formulas on, matched Find in formulas, and showed in the formula bar
when a cell of its spill range was selected.
2026-10-09 23:25:58 -05:00
nihilist-narwhal 2a3d1ffd7f Protection: refuse every edit of a locked cell on a protected sheet
Only cell.set checked protection. The check now runs where every edit
is committed: an edit that changes the value or formula of a locked
cell on a protected sheet is refused with Excel's message, whichever
command made it (clear, paste, fills, setValues, replace, goal seek,
sort, insert/delete cells...).

Inserting and deleting rows and columns follow the protection's
options, and deleted lines must not hold locked cells.
2026-10-09 23:25:58 -05:00
nihilist-narwhal b53a190ea1 Names: keep sheet-level names with their sheet
Moving a sheet left its sheet-level names on whatever sheet took its
index, and copying it shifted nothing and copied no names. Moving now
re-indexes name scopes, and a copy gets copies of the sheet's names,
referring to the copy, as in Excel.

Deleting a sheet left names that referred to it pointing at nothing;
they now become #REF!. formulas.deleteName honours scope, and the Name
Manager's Delete passes it.
2026-10-09 22:52:49 -05:00
Harrison O'Connor-Hoover 198e8ecb05 Issue #28: retain quick selection drag endpoints and clarify fill handle 2026-10-09 23:05:46 -04:00
Harrison O'Connor-Hoover b2386e1c23 Issue 30: preview border presets and clear shared edges 2026-10-09 22:45:41 -04:00
neko233-com 12bfdb0798 i18n: add Chinese, Korean and Russian interface languages 2026-10-10 10:24:09 +08:00
harrisonoconnorhover c51e51fe7b #92: import XLSB worksheet data and saved formula results 2026-10-09 22:19:36 -04:00
harrisonoconnorhover da0bf32d8a #16: import ODS worksheet data without overwriting source files 2026-10-09 22:09:09 -04:00
KitsuneSemCalda 6d47626534 Add OmaStore manifest 2026-10-09 22:50:39 -03:00
harrisonoconnorhover daf4591da0 Expose basic file operations in the shared ribbon (#93) 2026-10-09 21:35:53 -04:00
harrisonoconnorhover 922847a1ed Add system-following interface appearance (#72) 2026-10-09 21:35:21 -04:00
harrisonoconnorhover 91d44508b1 Correct screenshot timeout recovery advice (#78) 2026-10-09 21:27:18 -04:00
harrisonoconnorhover 698a2a8009 Keep keyboard navigation separate from pointer clicks (#87) 2026-10-09 21:26:06 -04:00
Brandon ThomasandClaude Opus 5.5 13664fe66b Bump version to 0.4.0 (from 0.3.0) for release; refresh contributors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.4.0
2026-10-09 21:02:14 -04:00
Dylan M. Taylor 2b1da77e46 chore: bump github actions to fix Node 20 deprecation warnings 2026-10-09 20:08:28 -04:00
nihilist-narwhal 311b1a593f Contributors: add nihilist-narwhal 2026-10-09 17:14:59 -05:00
nihilist-narwhal cd083cfc50 XLSX: cache #SPILL! and #CALC! as #VALUE!
They are not error codes of the file format, and Excel refuses to open a
file whose cached values contain them ("Parameter error"). Any workbook
with a blocked spill could not be opened in Excel. Excel caches these
errors as #VALUE! itself.
2026-10-09 17:14:59 -05:00
harrisonoconnorhover 628732ef58 Enable inline editing of worksheet text boxes (#56) 2026-10-09 17:47:58 -04:00
Eduardo Soares f424d7d56b i18n: Brazilian Portuguese interface language (#79)
Adds PtBr to the interface-language system from #31: a PORTUGUESE table
covering the same 259 chrome strings as JAPANESE (a test keeps the two
tables in step), Excel's pt-BR wording throughout, "pt"/"pt-br" codes
(serde-stored as "pt"), Português (Brasil) in the switcher, the
app.language.portuguese command, and first-run detection of a pt-BR
system locale via the existing tag reduction.
2026-10-09 18:18:57 -03:00
Dylan M. Taylor 4d46b9170b ci: publish Flatpak repository to Pages 2026-10-09 17:14:43 -04:00
DWAA1660andCodex 165e6c8a3b M2: add a vertical worksheet scrollbar (#73)
Reserve a strip beside the virtual grid for a theme-aware vertical
scrollbar. Support paging, dragging and wheel scrolling without changing
the active cell. Keep the scroll extent fixed during thumb dragging.

Co-authored-by: Codex <noreply@openai.com>
2026-10-09 14:46:29 -06:00
DWAA1660andCodex 96b3ab3244 M2: document the supported offscreen snapshot example (#71)
Point both contributor instruction files to the UI crate's snapshot
example and identify it as a command to run from a source checkout.
Verified the documented command produces a readable sales dashboard PNG.

Co-authored-by: Codex <noreply@openai.com>
2026-10-09 14:37:52 -06:00
DWAA1660andCodex d7174f8297 M2: preserve inherited formatting when entering empty cells (#70)
Resolve input styles from the destination cell, row or column before
parsing. Preserve inherited alignment, borders and number formats for
single-cell and bulk entry, including text paste and array formulas.

Co-authored-by: Codex <noreply@openai.com>
2026-10-09 14:33:43 -06:00
MarekSpodBiedry b8b3ba0b0e XLSX: write _xlpm. prefix on LET/LAMBDA parameter names, strip it on read (#63) 2026-10-09 20:57:44 +02:00
lloydandClaude Opus 5.5 631a49ec30 Recalc: linear in the number of range formulas
Opening a workbook with a SUM(E:J) on each of 50,000 rows took 9 s
(1.7 s at 25,000, 0.4 s at 12,500), and editing a cell that 50,000
VLOOKUPs read froze the app for 40 s or more. Two things grew with the
number of ranges:

- Evaluating a range trims it to the sheet's used range, and
  Sheet::used_range walks every stored row. It ran once per range read,
  so a full recalc was rows x range formulas. The pass's host now works
  it out once per sheet (the workbook doesn't change during a pass);
  Host::used_range keeps the old behaviour for other hosts.
- The reverse index kept each column's range precedents in a Vec:
  finding a cell's dependents scanned every range in the column, and
  re-entering a formula ran a retain over each of its columns. Pasting
  a column, filling formulas down or sorting was quadratic. Each column
  (and each sheet's wide ranges) now has an interval index: sorted by
  start, with a segment tree of the largest end, so a lookup costs
  O(log n + matches). Removals mark entries dead and insertions wait in
  a small sorted set; Graph::settle merges them after each batch, so
  edits cost O(log n) amortised. Single-cell dependents are a set, so
  removing one is O(1). dependents_of_range asks each column once
  instead of once per cell.

Semantics are unchanged: whole rows and columns, other sheets, names,
tables, spills and volatile functions go through the same paths.

Release CLI, open + full recalc: 12.5k rows 0.39 s -> 0.06 s, 25k 1.83 s
-> 0.14 s, 50k 9.3 s -> 0.28 s. The 50k x 20 bug-hunt workbook: recalc
on open 87 s -> 1.7 s, editing C2 (100k dependents) 51 s -> 1.3 s;
filling 50k SUMs down 17 s -> 0.18 s, sorting 50k rows 9 s -> 0.14 s.

Tests: the interval index against a linear scan (inserts, removals,
revivals, merges) and its query cost in tree nodes; range, wide,
whole-column, whole-row and cross-sheet dependents after edits; and a
scaling test (open, change a column, re-enter every formula) that
allows 8x the time for 4x the rows. Before the fix it took 16x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:51:15 +02:00
lloydandClaude Opus 5.5 8fe3276cb8 SUMIF/AVERAGEIF size the sum range like the criteria range
Excel adds up the range that starts at sum_range's top-left cell and has
the criteria range's height and width, whatever size sum_range has. With
A1:B3 = a,1 / b,2 / a,3, =SUMIF(A1:A3,"a",B1) is 4; GridCraft gave 1,
because if_numbers only saw sum_range's values (one cell) and read them
at the criteria positions. Its comment promised the resizing, but by then
the argument was a plain value.

The evaluator has the references, so it now does the resizing, as it does
for OFFSET and INDEX: for SUMIF and AVERAGEIF with three arguments, a
single-area sum range reference becomes resized_sum_range(sum, criteria
height, criteria width), cut off at the sheet's edge. It works across
sheets and through names. The dependency graph records the resized
range, so editing a cell inside it recalculates; when the criteria range
isn't a plain reference the formula is recalculated every time instead.
SUMIFS and AVERAGEIFS keep requiring same-size ranges (#VALUE!
otherwise).

Tests: a single-cell, shorter, wider, longer and horizontal sum range,
whole-column criteria, editing a cell of the resized range, the sheet
edge, another sheet and a defined name, and SUMIFS/AVERAGEIFS size
mismatches. They fail before the fix (1 instead of 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:44:18 +02:00
justinjohn0306 69709bd458 Line charts: smooth (spline) curves (issue #52)
Add support for Excel's "smoothed line" chart style. The smooth flag flows
model -> resolve -> chart render, persists through XLSX (<c:smooth val="1"/>),
and is toggled via the chart.addElement "smooth" element.

Rendering interpolates each gap-free run with a uniform Catmull-Rom spline
(0.5 tangent scale, 100 sub-segments per data segment), so the curve passes
through every data point and rounds the vertices — matching Excel's observed
behaviour. Long runs are still decimated before smoothing.

- model: Series.smooth (#[serde(default)], so old workbooks load unchanged)
- chart: SeriesData.smooth; smooth_run + catmull in render; wired through
  resolve and the sanitize() copy; draw_runs takes the flag (line + scatter)
- engine: chart.addElement element="smooth"
- xlsx: read/write <c:smooth val> instead of hard-coding val="0"
- tests: smooth_line_adds_interpolated_points_and_keeps_endpoints,
  smooth_line_splits_at_gaps, smooth_line_setting_round_trips
- examples/smooth_demo.rs: generates a straight+smooth side-by-side workbook

cargo xtask ci: all 7 steps pass.
2026-10-10 00:09:12 +05:30
lloydandClaude Opus 5.5 ecaead4c12 DATE(1900,2,29) is serial 60, as in Excel
serial_from_ymd worked out the real calendar date and then moved serials
up to 60 down by one. February 1900 has no 29th in the real calendar, so
DATE(1900,2,29) and DATEVALUE("2/29/1900") rolled over to 1 March (61),
and DATE(1900,3,0) gave 28 February (59). Excel's 1900 system keeps the
Lotus leap day: serial 60 is 29 February 1900, and DAY(60) already
said so.

The serial of the first of the month is now taken in Excel's calendar
(one lower up to February 1900), and the day, with any overflow, counts
on from there. So day and month overflow pass through serial 60 both
ways: DATE(1900,1,60), DATE(1900,4,-31) and DATE(1901,-10,29) are 60 too.
Month and day arithmetic is checked, so hostile values return None.

days_in_month_in gives February 1900 29 days in the 1900 system; EDATE
and EOMONTH use it, so EOMONTH(60,0) is 60 rather than the day before,
and the text parser uses it in place of its own special case. The 1904
system is unchanged (1900 is before its epoch).

Tests: serials around the leap day (overflow both ways, every serial
0-366 round-trips through its year, month and day), hostile months and
days, parsing 2/29/1900 and 1900-02-29, DATE/DATEVALUE/EDATE/EOMONTH
in the 1900 system and the 1904 system. The DATE, DATEVALUE and serial
cases fail before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:36:08 +02:00
MarekSpodBiedry 2c2d92ef6b ROUND: keep 15 significant digits when asked for 15+ digits, so ROUND(0.1+0.2,15) is 0.3 (#58) 2026-10-09 19:29:29 +02:00
lloydandClaude Opus 5.5 903bbb096d Issue #51: General numbers fit by the cell's own font, not 11pt
grid.rs measured the digit width once, for an 11pt cell font, and
display_text judged how many General digits fit from that whatever the
cell's size. 9pt marks such as 100 in columns with a stored width of 4
(common in mark sheets) came out as ##. The width is now measured per cell
with its own font and size, as print.rs already does; the shared 11pt
measurement and its paint_quadrant parameter go away.

Test: a 9pt 100 in a 28px column draws in full, while 123456789 next to it
still shows ####. Fails before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:23:15 +02:00
lloydandClaude Opus 5.5 ad6e77f6ef Issue #48: rotated text with Wrap Text draws upright (grid and print)
Narrow columns with tall, upright headers (mark sheets) set both a text
rotation and Wrap Text. The wrap path drew the text flat and returned before
the rotation was applied, so headers came out as 3-4 character fragments.

- Rotated text no longer takes the flat wrap path. Upright (+/-90 deg) text
  wraps to the row height, its lines stacking across the column; other
  angles keep one line, as before.
- Downward text was anchored at the bottom-left and ran out of the cell
  (over the row headers); it now starts at the top, one block-height in.
- Upright text no longer overflows into empty neighbours: its width is its
  line height, and the overflow repainted their gridlines.
- Print/PDF gets the same three fixes.

Tests: ui-egui interaction test (shapes drawn at +/-90 deg, inside a
one-column clip) and a print test (every text block on the page rotated,
a few long lines each). Each failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:13:40 +02:00
harrisonoconnorhover 1d2df6126b Dismiss worksheet popups on outside clicks and focus loss (#41) 2026-10-09 10:32:07 -04:00
harrisonoconnorhover 38c11f6fb9 Fix header double-click AutoFit for selected ranges (#42) 2026-10-09 10:23:09 -04:00
harrisonoconnorhover b36f919410 Fix editing and formula bar reads in merged cells (#40) 2026-10-09 09:51:08 -04:00
harrisonoconnorhover 56c32750a2 Add initial ribbon keytips and legacy shortcuts for #33 2026-10-09 09:44:04 -04:00
Brandon Thomas 51b531be3e Merge pull request #13 from XusBadia/dmg-background
macOS DMG: branded window background and icon layout
2026-10-09 09:20:56 -04:00
justinjohn0306 6beb80ed36 Fonts: render Thai and Hebrew (and any non-Latin) instead of tofu (issue #32)
egui bundles only Latin glyphs, and the fallback chain appended just the
Han/Kana and Hangul faces, so Thai and Hebrew text rendered as tofu boxes
even though the text was read correctly.

Generalise the CJK fallback into a per-script fallback: every family now
also carries a Thai face (Leelawadee UI / Tahoma / Thonburi / Noto Sans
Thai) and a Hebrew face (David / Arial Hebrew / Noto Sans Hebrew). epaint
shapes the complex scripts itself via harfrust -- it only needed a face
that has the glyphs. The faces stay at the end of each chain, so Latin
still wins for Latin text.

The tests become script-general: a structural check that every family
ends with the loaded fallback faces, and a per-script render check that
the sample string resolves to a real glyph when the face is present.

Full bidi reordering of mixed LTR/RTL text is a separate epaint limitation
(no Unicode Bidi Algorithm); a pure other-script run is shaped correctly.
2026-10-09 17:06:36 +05:30
harrisonoconnorhover 51f3279135 Fix #23: copy selected cells as HTML tables 2026-10-09 05:56:08 -04:00
harrisonoconnorhover 28113c6c75 Fix #26: paint large ribbon dropdown chevrons 2026-10-09 05:28:48 -04:00
MarekSpodBiedry 4dd49f7b95 Fix: inserting or deleting rows, columns or cells cancels copy/cut mode (#21) 2026-10-09 10:45:50 +02:00
justinjohn0306 e8e010b446 i18n: switch the interface language (issue #20)
Add an interface-language preference. It defaults to the system's
language on first run (sys-locale, native only) and is switched by hand
from the View tab, next to Dark Mode, or with app.language.english /
app.language.japanese over the control channel. It is saved in ui.json.

Follows the filmcraft house convention: a Language enum and a
table of English-to-Japanese strings with English fallback, so coverage
can grow incrementally. Japanese chrome renders without any bundled
font: the CJK fallback faces already appended to every family cover
kana and kanji.

The ribbon chrome all flows through the small_button/big_button/
toggle_button/split_button/icon_button helpers and menu_items, so
translating there localises the whole ribbon in one place; the language
of the frame is read from egui context data (i18n::current). Command
ids, file names and document text are never translated.
2026-10-09 13:14:48 +05:30
justinjohn0306 7e24aa398a CI: satisfy newer clippy lints (toolchain drift, behavior-preserving)
Local clippy 1.96 flags pre-existing patterns the code has always had:
manual range-contains comparisons, a dead `|| true` filter and a
simplifiable boolean. None change behaviour; they only clear the lints
so `cargo xtask ci` passes on a current toolchain.
2026-10-09 13:08:28 +05:30
Xus BadiaandClaude Opus 5.5 be33eba7d1 macOS DMG: branded window background and icon layout
The DMG opened as a plain Finder window. It now shows the app icon's figure on
the GridCraft colour, with GridCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's (storytold/photocraft#1201,
storytold/vectorcraft#493).

- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
  the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
  16-colour palette, Deflate, sRGB; ~26 KB)
  and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
  positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
  reproducible, with nothing from the machine that ran it (no paths, users,
  disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
  hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
  an alias that includes the volume name. The DMG file name keeps the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:43:50 +02:00
justinjohn0306 891514892a CJK text renders as tofu: fall back to a system CJK font
A Japanese (or Chinese/Korean) workbook opened fine — the engine reads the
strings correctly — but every kanji and kana drew as a .notdef box, because
egui's bundled fonts carry no CJK coverage and nothing added a CJK face to the
fallback chain.

Append a system CJK face to every font family (UI, cell, serif, mono): a
Japanese face first for correct kana/JIS kanji, a Chinese face for breadth, and
a Hangul face for Korean. epaint picks the first face in a family that has the
glyph, so Latin still renders from the Latin fonts and only Han/Kana/Hangul
reach the CJK fonts. No font bytes are bundled (AGENTS.md): families are loaded
from the OS, and a machine with no CJK font simply keeps the old behaviour.

Tests: the CJK keys end every family chain (Latin stays first); and the
proportional family resolves 「カテゴリ」 to real glyphs, not the replacement
character — verified to fail without the fallback.
2026-10-09 10:13:44 +05:30
DWAA1660andClaude Opus 5.5 881bab8b6a Default Windows startup to DirectX 12
eframe's default wgpu instance enables PRIMARY | GL on Windows. Creating
the OpenGL backend can crash inside AMD's atio6axx.dll before the window
appears, so the app exits silently at launch. When WGPU_BACKEND is not
set, the Windows instance now enables only DirectX 12; an explicit
WGPU_BACKEND is still honoured. macOS, Linux and web are unchanged.

Same fix as storytold/designcraft#168 and storytold/filmcraft#179.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyVbT6Vv4BwWZ4hY8973qD
2026-10-08 19:40:12 -06:00
justinjohn0306 596b465bb1 Macros UX: Excel-style info bar, Macros dialog, status badge (issue #6)
The engine now preserves a workbook's VBA project, but the UI had no surface
for it and silently dropped every open-time warning. Give preserved macros an
honest, Excel-shaped home without a false "Enable Content":

- Info bar under the ribbon (Excel's message-bar slot) showing the warning a
  `file.open` result carries. A VBA project gets an amber bar reading "This
  workbook contains VBA macros. GridCraft keeps them intact when you save, but
  does not run them." with a "What this means" button; anything else becomes a
  blue informational bar. Dismissable; a clean open clears a stale notice.
- Macros dialog (Automate ▸ Macros, Alt+F8): lists GridCraft's own scripts and
  the preserved VBA project (presence + size, not module names — the blob is
  opaque). Mirrors Excel's Developer ▸ Macros without claiming to run VBA.
- Status-bar badge "Macros preserved (not executed)" whenever the workbook
  carries a VBA project.

New panel `notice_bar` reuses theme tokens (notice_warn_*/notice_info_*), so it
follows light/dark. Two new UI commands, `ui.notice` and `ui.notice.dismiss`,
reach it from the control channel.

Tests: info bar appears for a macro workbook and clears on dismiss; a non-VBA
warning becomes an info bar; clean open clears the notice; the macros dialog
lists the VBA entry; status bar renders with a VBA project; Alt+F8 opens the
dialog. Snapshot-verified in light and dark.
2026-10-09 05:20:04 +05:30
justinjohn0306 9c1d2c3b3a Add scripts/check-vba-roundtrip.sh: verify VBA preservation on a real file
Builds a macro-enabled .xlsm with a fake xl/vbaProject.bin, runs it through
`gridcraft-cli run`, and asserts the blob comes back byte-identical with the
macro-enabled content type and vbaProject relationship intact — the
open → edit → save path a user would exercise by hand.
2026-10-09 01:58:29 +05:30
justinjohn0306 726a31740e Preserve VBA macros on round-trip; never destroy a macro-enabled workbook (#6)
Opening an .xlsm and saving it silently stripped xl/vbaProject.bin, turning a
macro-enabled workbook into a macro-free one — data loss for anyone whose work
depends on macros. GridCraft still does not run VBA, but it now carries the
project through unchanged.

- model: `Workbook.vba_project: Option<Vec<u8>>` (base64 in JSON via a new
  `b64::opt` helper). `None` = no macros.
- xlsx read: capture the `vbaProject` relationship's bytes instead of warning
  and dropping them; warn that macros are kept but not executed.
- xlsx write: when present, emit `xl/vbaProject.bin` byte-identical, add the
  `.../relationships/vbaProject` relationship, switch the main part to the
  macro-enabled content type (`...sheet.macroEnabled.main+xml`) and add the
  `application/vnd.ms-office.vbaProject` override.
- engine: `view.macros` lists the preserved project alongside GridCraft's own
  scripts (`{"kind":"vba","executed":false}`), so callers can see it is there.
- ROADMAP: scope now "preserve, don't execute" (was "out of scope").

Tests: round-trip byte-identical blob + correct content types/relationship;
capture from a foreign-produced .xlsm; a macro-free workbook stays clean.
2026-10-09 00:54:10 +05:30
justinjohn0306 d9fd1b7305 CI: satisfy newer clippy lints (toolchain drift, behavior-preserving)
Local clippy (1.96) flags pre-existing code that an older toolchain accepted;
these lints fire on clean main and block `cargo xtask ci`. All rewrites are
behavior-preserving:

- core/date.rs, numfmt/render.rs, functions/math.rs, functions/stat.rs:
  `!x.is_finite() || x < a || x >= b` -> `!(a..b).contains(&x)` (Range::contains
  is false for NaN/inf, matching the existing is_finite guard).
- functions/util.rs: `!(A && !B)` -> `!A || B`.
- engine/cmd/file.rs: drop a dead `.filter(|| true)`.
- ui-egui/grid.rs: `find(|| true)` was always the first table (use `.first()`);
  `wrap || Justify || (nl && wrap)` -> `wrap || Justify`.
- ui-egui/formula_bar.rs: `tab || (enter && false)` -> `tab`.
- engine/cmd/spelling.rs: split the wasm/non-wasm dictionary init so the
  non-wasm `mut` no longer warns under wasm check.
2026-10-09 00:53:54 +05:30
justinjohn0306 2276eabba8 File open: recognize Excel password-protected (CFB) files, report clearly (#11)
Excel-encrypted workbooks are CFB/OLE2 compound files (magic D0CF11E0),
not zip packages. sniff() now returns Format::Encrypted for them, and
open_bytes reports a clear password-protected message instead of the
cryptic 'not a valid zip package' error.
2026-10-08 21:04:07 +05:30