arca-gui migrates from egui to GPUI and redesigns the window with GPUI Kit - #1
Merged
Merged
Conversation
La rama anterior se hizo sobre c4c0354 y main se movio 18 commits por debajo: renombrar dentro del archivo, el visor, la vista plana, los grupos por mascara, las columnas y el modo oscuro en blanco y negro. Los dos lados reescribieron main.rs desde la misma base, asi que el merge salieron 46 hunks que son el mismo conflicto repetido: la rama renombro todos los accesos al estado y main escribio mil seiscientas lineas nuevas contra la forma vieja. Resolver eso no es elegir un lado, es rehacer la extraccion encima del main de hoy, y se hace asi a proposito: el compilador comprueba la transformacion. Un hunk mal resuelto compila; un self.archive que se escape de la reescritura, no. Este commit trae solo lo que no toca main.rs y por tanto no choca: el spike, los planes, Cargo.lock fuera de .gitignore y las dependencias de GPUI Kit. gpui_shell.rs y gpui_theme.rs vienen en el arbol pero main.rs todavia no los declara, asi que no se compilan: la ventana GPUI se enciende en el commit siguiente, cuando exista AppController. El analisis de esa extraccion --que campo va donde, cuales de los 57 metodos se mueven, y el unico sitio que no es mecanico-- esta en docs/plans/portar-controlador-a-main.md para que no viva en la cabeza de nadie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Faltaba lo que una sesion nueva no puede deducir del codigo: en que rama esta cada cosa, y sobre todo que la rama vieja no se borra porque su main.rs es la implementacion de referencia de AppState, AppController y AppAction. Rehacerlos desde cero seria trabajo tirado. Tambien la lista de comprobacion manual. cargo test no toca la UI de egui, asi que si la extraccion se come el visor o los grupos por mascara, la suite pasa verde igual y nadie se entera. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Villoh
force-pushed
the
gpui-kit-redesign
branch
from
September 8, 2026 19:48
c96f88f to
53bf201
Compare
# Conflicts: # arca-gui/Cargo.toml # arca-gui/src/main.rs
El shell GPUI llevaba 149 de los 161 textos en ingles fijo dentro del codigo: en espanol se veia media ventana sin traducir y un lector de pantalla leia los nombres de las regiones en ingles. Ahora sale todo de i18n.rs. Diecisiete cadenas nuevas, las que GPUI necesita y egui no tenia: los nombres de las regiones que solo existen aqui -- barra de acciones, estado, progreso, contenido -- y los estados vacios, que en egui eran una sola frase y aqui distinguen archivo sin entradas, carpeta vacia y filtro sin coincidencias.
Idioma, tema, y el formato, compresor, nivel y subcarpeta con que se crea un archivo nuevo. Hasta ahora la superficie GPUI leia gui.conf al arrancar y no habia forma de tocarlo sin editar el fichero a mano. Cada preferencia es una fila de botones con el actual relleno, no un desplegable: hay tres como mucho de cada, y una lista asi de corta cuesta mas abrirla que leerla. El teclado y el raton pasan por settings_activate, que es una sola funcion, para que Enter y el clic no puedan acabar haciendo cosas distintas.
Solo habia Ctrl+F, C, X y V. Ahora tambien Ctrl+O, Ctrl+N, Ctrl+E, Ctrl+T, Ctrl+I, Ctrl+Shift+C, Alt+W, F5, F1 y Escape, y una ventana F1 que los lista. Un solo manejador en la raiz en vez de una docena de acciones GPUI con sus bindings: cada atajo es la misma forma -- una tecla, una guarda y una accion ya escrita -- y una tabla de ellos se lee de una vez. La lectura de la tecla sale a shortcut_for, que no toca estado, para poder comprobar sin ventana que Ctrl+O sigue valiendo con el cursor dentro del filtro y que F5 no.
La superficie GPUI no tenia menu contextual: abrir, extraer la seleccion, extraer aqui, probar la seleccion, quitar, copiar, cortar, pegar, copiar nombres y seleccionar todo solo estaban en la barra, en el menu de overflow o en una tecla que habia que saberse. Cada entrada nombra su atajo, para que el menu se deje de usar solo. Copiar, cortar y pegar se quedan fuera donde el escritorio no tiene portapapeles de ficheros, en vez de aparecer en gris: una entrada que no puede hacer nada nunca es peor que no tenerla. Pulsar donde no hay menu lo cierra; el menu esta occluded, asi que sus propios clics no llegan al fondo.
El ancho estaba clavado en el codigo, uno por columna, y no se guardaba. Ahora sale de Settings::widths, el mismo campo de gui.conf que llevaba usando la ventana egui, asi que una anchura puesta en una superficie aparece en la otra. El tirador va absoluto dentro de la celda de cabecera, no como elemento de la fila: un separador con ancho propio correria cada cabecera unos pixeles respecto a la columna que nombra. El raton se sigue en la ventana y no en la tira, porque el puntero se sale de seis pixeles enseguida. Doble clic ajusta la columna a lo que lleva dentro. El nombre sigue siendo elastico en vez de tener ancho fijo: es lo que hace que la lista llegue al canto derecho.
Todo el archivo de una vez, sin carpetas, que es como se busca algo cuando no se sabe donde esta. El controlador ya lo tenia en visible_rows leyendo settings.flat; lo que faltaba era la forma de encenderlo. Al encenderla se enciende la columna Ruta si estaba apagada, porque una lista sin carpetas encima tiene que decir de donde sale cada nombre.
Faltaba todo lo que escribe: anadir ficheros, nueva carpeta, renombrar, deshacer, guardar una copia, contrasena por defecto y seleccionar por mascara. La superficie GPUI solo sabia abrir, extraer y borrar. undo_last, drop_undo y reread_names se van de la vista egui al AppController. No tienen nada de un toolkit dentro y las dos superficies las ofrecen; tenerlas colgando de ArcaApp era la razon por la que GPUI no podia deshacer. Los cuatro dialogos que piden un texto comparten un solo campo con el nombre que le pone el dialogo abierto, porque son modales y excluyentes entre si: un campo con tres nombres es un campo, no tres que estan vacios casi siempre. El mismo motivo por el que el volcado de lo tecleado sale a push_to_owner: habia dos copias identicas y un campo nuevo se olvidaba en una de ellas.
Los recientes cuelgan del menu, diez como mucho, por ruta y no por nombre para que dos que se llaman igual se distingan, con su escoba al final. La pagina de codigo se va a los ajustes en vez de a un submenu: es una preferencia guardada en gui.conf, y ese es el sitio de las preferencias guardadas. Cambiarla relee los nombres del archivo abierto en el sitio, sin volver a abrirlo.
F3, o el menu de la fila, abre lo que hay dentro del archivo sin sacarlo: como texto, como hex o como la imagen que es. El controlador ya sabia leerlo -- view_entry estaba en AppController -- y GPUI no tenia donde ponerlo. El texto y el hex van por uniform_list, asi que solo se maquetan las lineas en pantalla y un log de un millon de lineas se abre igual de rapido que una nota de tres. La imagen se decodifica una vez y se guarda con el nombre del que salio: darle a GPUI una Image nueva por fotograma seria decodificar una foto de treinta megas sesenta veces por segundo. La pestana de imagen solo aparece cuando hay imagen, y la fila de una carpeta no ofrece visor: una pestana que dice imagen sobre un fichero de texto es una pestana que miente.
El merge f10f5b4 se quedo con el main.rs de la rama tal cual y con el i18n.rs de main tal cual, asi que la ventana egui perdio features que si estaban en 0.6.6. moving_word, que nadie usaba en ninguna de las dos superficies, era la senal. Vuelven Job::Move, moved_name con su prueba de las dos escrituras de barra, y move_into, ahora en AppController y no colgando de la vista egui. Con ellos vuelve tambien el apunte de deshacer en run_job: sin el, state.undo no se escribia nunca y Ctrl+Z y su entrada de menu estaban apagados para siempre. En GPUI el gesto es el mismo que en egui: mientras la seleccion esta en el aire no se decide nada. Si cae sobre una carpeta del archivo, se mueve ahi; si sale de la lista, la coge arca-drag. El arrastre nativo no puede empezar antes, porque en cuanto empieza el sistema se queda el puntero y ya no hay vuelta a la lista.
Lo otro que se llevo por delante el merge f10f5b4. arca-net seguia en Cargo.toml sin que nadie lo llamase, y update_ready, check_updates, update_tampered y update_installing estaban en i18n.rs sin usar: esa era la senal. Vuelve el aviso entero -- preguntar una vez al arrancar, en un hilo y sin decir nada; la entrada arriba del menu con un punto en el boton para que se vea desde fuera; bajar el instalador comprobando el SHA-256 contra las sumas publicadas al lado; y dejar que Inno cierre y reabra Arca. Una copia sacada del .zip no se actualiza sola, porque son ficheros sueltos en una carpeta que eligio alguien: a esa se le ofrece la pagina. Solo se aceptan direcciones https de nuestro propio repositorio. Y con ello Settings::save, que construia las lineas de flat, tree, page, window y recent en un String que luego tiraba. Seis ajustes se leian al arrancar y no se escribian nunca: por eso la lista de recientes salia siempre vacia. El texto del fichero sale ahora a Settings::text y la prueba lo lee de vuelta sin tocar disco, que es la unica forma de que no se vuelva a romper en silencio.
La tercera que se llevo el merge, y con ella lo que nadie usaba en i18n.rs: elapsed_word, time_left, pause_word, resume_word, paused_word y stopping. Con ella vuelve tambien Cancelar, que estaba muerto: AppAction::CancelJob era un brazo vacio y el notify del worker devolvia true siempre, asi que el boton no paraba nada. Ahora hay dos banderas -- parar y esperar -- que el worker lee al acabar cada entrada, que es el unico momento en que no esta en mitad de algo. Pausar suelta al final de una entrada, no de un byte: un fichero empezado tiene que terminar. Parar pasa aunque este en pausa, para no tener que reanudar algo solo para poder abandonarlo. Las banderas se renuevan por trabajo en vez de bajarse: un hilo al que se le dijo que parase puede seguir de salida, y no debe leer la bandera que mira el trabajo siguiente. El trabajo vuelve a distinguir si se lanzo desde la lista -- panel encima, con el fondo atenuado -- o desde el Explorador, que es la ventana entera porque no hay lista detras a la que volver. En GPUI la tira de progreso gana el reloj, la estimacion y el boton de pausa.
Un trabajo abandonado decia en rojo lo que dijese el error de dentro. No hay nada mal en el archivo: la reescritura se rindio antes de cambiar nada, y eso es lo que dice ahora, en el color de lo que solo es una noticia. Era la ultima cadena de i18n.rs que no usaba nadie.
Los dos gestos de puntero que faltaban. La lista de GPUI es virtualizada, asi que sobre que fila esta el raton es aritmetica con el desplazamiento y no un rectangulo que alguien haya guardado: eso sale a row_under, que se comprueba sin ventana porque una fila de diferencia ahi seleccionaria todo corrido un sitio. La goma sigue la regla del Explorador, que es la unica que deja los dos gestos en el mismo boton: pulsar sobre algo ya marcado y tirar lo lleva a otro sitio, pulsar en cualquier otro lado y tirar dibuja una seleccion nueva. Ctrl suma a lo que habia. Pasado un borde la lista corre detras del puntero, porque si no una seleccion nunca podria ser mas larga que la ventana. La rueda pulsada deja un ancla y la lista corre hacia el puntero, mas rapido cuanto mas lejos, con wheel_speed, que es la misma funcion y las mismas pruebas que usa egui. La suelta cualquier otro boton, la propia rueda girando o Escape. Dos diferencias con egui, a proposito: el paso lo da el tick de 100 ms que ya tenia el shell en vez de un fotograma, y el ancla es un anillo sin las flechas que Windows dibuja sobre el puntero, porque eso pedia esconder el cursor del sistema.
Se veia OpenEnter, ViewF3, RemoveSupr. Las entradas se construian con una tabulacion dentro del texto, que es como egui dibuja una columna a la derecha; GPUI maqueta texto y ahi una tabulacion no es nada, asi que el atajo quedaba soldado a la palabra. El atajo pasa a ser un hijo aparte, alineado a la derecha y en la tinta apagada, porque es una forma de llegar y no una segunda cosa que leer. Como ya no hay que meterlo en la cadena, lo tienen todas las entradas que lo tengan y no solo las tres del portapapeles: probar, seleccionar todo, invertir, soltar, deshacer y contrasena por defecto tambien lo dicen ahora. Y vuelven las rayas que agrupaban ese menu en egui: mirar, cambiar, mover por el portapapeles y trabajar sobre la seleccion son cuatro cosas distintas, y doce entradas seguidas son un muro.
La leyenda nativa se queda transparente y la dibuja TitleBar de GPUI Kit, con los mismos tokens que todo lo que hay debajo: el arrastre, el doble clic para maximizar y los tres botones de la ventana los lleva ella. Dentro va el nombre de lo que esta abierto, que es para lo que sirve una barra de titulo. Nada pulsable ahi dentro a proposito: la barra es la zona de arrastre, y un control dentro de ella movería la ventana en cuanto la mano temblase de camino a pulsarlo. Dos cosas que el alto nuevo movia. El menu de overflow flota en coordenadas de ventana, asi que ahora cuenta la barra o se dibujaba encima de la fila de botones en vez de debajo. Y los dos velos que tapan el fondo -- los dialogos y el selector de ficheros del sistema -- empiezan bajo la barra en vez de cubrirla: los tres botones de la ventana no son fondo, y un dialogo que los tapase seria un dialogo que no deja cerrar la ventana que hay detras.
… ventana Dos fallos distintos con la misma cara. La goma leia el alto de fila de last_item_size.item, que pese al nombre es el tamano del viewport y no el de una fila: dividia por cuatrocientos en vez de por veintiocho, asi que todo punto caia en la primera fila y no se marcaba nada. El alto sale ahora del contenido entre el numero de filas, que es de donde GPUI lo saco, y la division va a row_height con su prueba para que el nombre no vuelva a enganar a nadie. Y arrastrar fuera no hacia nada porque AppController::drag_out era un cuerpo vacio: la implementacion de Windows con arca-drag tambien se la comio el merge f10f5b4, y arca-drag llevaba desde entonces en Cargo.toml sin que la llamase nadie. Vuelve entera, sin extraer nada al empezar: al shell se le da una lista de nombres y tamanos y va pidiendo un fichero cada vez mientras suelta, asi que un arrastre del que uno se arrepiente no cuesta nada. El disparo pasa de la fila a la ventana. on_drag_move se dispara con cualquier movimiento, y comparando contra el rectangulo de la fila el arrastre nativo empezaba en cuanto el puntero llegaba a la fila de al lado -- que no es salir de la lista, y ademas hacia imposible soltar sobre una carpeta. Ahora se mira una sola vez, contra la lista entera.
Dos costes, los dos por movimiento del raton, que es a cien hercios. visible_rows ordenaba llamando a to_lowercase() dentro del comparador: dos String por comparacion, o sea unas treinta mil asignaciones por lista de mil quinientas filas. Ahora se pliega caracter a caracter con Iterator::cmp, sin asignar nada. Eso lo paga todo el que ordene, tambien el repintado normal. Y la lista se construia entera dos o tres veces por evento. La goma se la queda congelada al empezar el gesto -- lo que marca no puede cambiar de donde lo marca, y con un boton bajado no hay nada mas que la cambie -- y lo unico que las demas necesitaban de ella era cuantas filas hay, que ahora lo apunta el repintado, que es el que ya lo sabia.
No habia perfil dev, asi que cargo run compilaba todo a opt-level 0: GPUI entero, su motor de layout, el modelador de texto y el rasterizador, que rehacen su trabajo en cada fotograma. Eso no es un poco mas lento, es la diferencia entre una lista que sigue al puntero y una que va a tirones al arrastrar y al marcar. Las dependencias pasan a 3 tambien en depuracion: no son codigo de Arca y nadie las depura. El codigo de Arca a 1, que es lo que necesitan ordenar la lista y recorrer las entradas para no notarse, y a ese nivel el depurador sigue parando donde se le dice. La primera compilacion despues de esto rehace el arbol entero -- seis minutos y medio aqui -- y a partir de ahi un cambio en arca-gui vuelve a ser ocho segundos.
The row div carried our own click listener and the kit chained its own on top of it, so a double click ran both: select_row navigated into the folder and the kit's DoubleClickedRow then resolved the old index against the already navigated list, opening the first entry inside it.
The folder pane was a fixed 224 pixels, so a deep branch could only be reached by scrolling a pane that had room to spare next to it. It now sits in a resizable split with the file list, between 160 and 520 pixels, and the width is kept with the column widths so it survives the window. The row width follows the pane instead of a constant, which also fixes the selected row's outline being clipped on its right edge: the sidebar's right border eats into the content box, so rows sized to the padding alone overflowed the scroll area by one pixel.
Crossing onto the sidebar counted as leaving the list, so the native drag started before a folder there could ever be dropped on, and the tree took no drop at all. Only the window's own edge counts on that side now, and the tree's folders take the same move a folder row takes. The preview was painted at the pointer less where inside the row the press landed -- a row is as wide as the list, so a press on a far column drew it back at the file name or half off the window. It is carried at the pointer now: centred over it, lifted above the hand, and opaque rather than a tint, which was unreadable over file names.
The em dash looked wrong in the title bar; a hyphen matches what every other window on the desktop uses.
Button renders its label in an inner element that applies button_text_size(self.size), so the .text_xs() set on the button itself was overridden by the default Medium size and every word button drew at 16px while the surrounding bars drew at 12px. The mismatch was visible in the breadcrumbs, where the current folder is a plain div. Setting the button size to XSmall resolves button_text_size to text_xs instead.
…a hit is The filter matched an entry's whole path across the whole archive and drew the result as that path, so searching "redist" inside "_CommonRedist" answered with every file in the folder, each one labelled with the way back to the archive root. A search now starts at the current folder -- the whole archive from the root, that folder's subtree from anywhere else -- and matches a name against its own leaf, never the folders above it. It answers with folders as well as files, and names a hit by the path from the searched folder down, so a result says which subfolder it came out of without repeating the path to the root. The optional path column is read from the same folder. A rename takes its starting name from the entry rather than from the row label, which now carries that folder while a search is showing.
…the tree Right clicking anywhere but a row did nothing: the kit only marks a row when the press lands on one, so the menu was built for a stale row or for none at all, and the sidebar had no menu at all. Both are places a file manager is expected to answer from. Under the last row the menu is the folder's: make a folder, paste, select all. On a branch of the tree it is the row menu, run against that folder -- opening, pasting and making a folder go inside it, and everything else picks it and works on the selection, so the list is never dragged somewhere to explain an action. The name is edited in the panel it was pointed at, which is why a rename started from the tree is typed there.
The tree was a drop target but never a source, so a folder the list is not showing could only be moved or extracted by navigating to its parent first. Pulling a branch now picks everything under it, the same carry a folder row of the list starts. The sidebar's rows begin above the list's, so a pull begun in the tree crossed the list's top edge on its first move and handed itself to the system as a native drag. Over the sidebar column only the window's own edge now counts as leaving.
Two ghost words at the end of a strip that already ends in grey status text read as the rest of the sentence, not as something to press. Pause, resume and close are marks nobody needs a tooltip for, and the kit's icon set already carries them.
The toolbar password button built its job with no new password in it and ran it, and nothing ever filled that blank, so the only thing the button could do was take a password off: on a plain archive it rewrote the file unencrypted without asking anything at all. The window now holds a second question. An archive that is already locked is asked for the password it has, and then, like every other archive, for the one it is about to get; an empty answer to that second question is what takes the password off, and the Remove password button is the same answer by name. Answering no longer closes the dialog unconditionally either: it closes only once nothing is left to ask, so an empty box or the first of the two questions leaves the window where it is instead of stranding it waiting on a dialog that is gone.
The password boxes carried a separate Show/Hide button underneath, driven by a flag on the application state that every frame pushed back into the field. The kit's own input has the eye for this, drawn inside the box on the right, so the button, the flag, the action that flipped it and the two words it needed are gone. Masking is the field's business now, which also means it can be reset: every password box opens hidden, where before revealing one left the next dialog opening in the clear.
The password kept for the window was written down and then read by nobody: Ctrl+P took one, said it would be used until the window closed, and every dialog went on asking from scratch. Both dialogs that ask for a password now offer it by name, next to the box and only when there is one to offer: the one that asks to open, unlock or lock an archive, and the one that compresses. It fills the box rather than answering for you, so the password can still be read, changed or cleared before the dialog is answered -- a password put on an archive by accident costs a full rewrite to take off again. Its Spanish name was also missing its tilde.
Compressing from the toolbar closed the whole program the moment the job finished, which reads as a crash. close_when_done was set for every Job::Compress and the shell answers that by removing the window. That is right for a window the Explorer opened to do one job and wrong for one being driven by hand, so it is now gated on a one_shot flag taken from the startup arguments. The add box could only ever be given files. Windows has one native dialog for files and another for folders and none for both, so the picker is now a single Add... button with an entry for each, and every pick adds to the list instead of replacing it. The box lists what it is about to compress, with a cross per row and a Remove all, because a selection built over several passes is only trustworthy if it can be read back before the work starts. Dropping files in from the Explorer never worked anywhere in the window. GPUI translates FileDropEvent::Entered into a MouseMove and Submit into a MouseUp before dispatch, so only Exited ever reaches a FileDropEvent listener and the window was watching for something that never comes. External files arrive as an ordinary drag carrying ExternalPaths, which is what the root and the add list now listen for -- and a drag is the one gesture on Windows that carries files and folders together, so a drop onto the add box goes straight into the list.
The archive was written to a path anchored at /tmp. Git Bash puts /tmp under AppData while the native python3 reads the same string as C:\tmp, so the fixture was never created, the extraction that followed failed for want of a file rather than for want of permission, no escaped file appeared, and the control reported a pass having run nothing at all. A security check that cannot fail is worse than no check, because it is counted. The archive is now built and checked relative to the working directory the script already moved into, and the entry escapes one level up so it lands somewhere both platforms can name. Building it is no longer assumed: if the fixture is missing the script says so instead of reading its absence as a refusal. The extraction is asserted to fail as well, so the pass needs the extractor to have actually refused and not merely to have left the filesystem alone. The protection itself was never in doubt and is unchanged: arca-core's safe_name rejects the entry, and covers this in its own tests.
The window sat on "Created ..." and an empty list, and the only way to see what had been built was to go and open it by hand. reread_target answered None for Job::Compress because compressing writes somewhere else and nothing that was open had changed, which was true and beside the point: what it wrote is an archive, and a window left sitting on its own success has nothing else to show. This only surfaced once the window survived the job. Until then it closed the moment compressing finished, so there was never anything to show it in. The two halves have to agree. A window the Explorer opened for one job still leaves when that job ends, and what decides it leaves is still being on View::Running -- which rereading is exactly what replaces with View::Browse. Sending such a window to the new archive would cancel its own departure, so the target is only set when the window is staying.
It was a quarter of a feature, and the quarter that pays least. The button never unlocked anything: it wrote the remembered text into the box that was already asking, in all four places one is asked for. That caution is right for setting a password, where one put on by accident costs rewriting the whole archive to take off, and pointless for opening one, where a failed attempt costs nothing. What was left saved typing a long password in exchange for two clicks, only within a session, and only after arming it with Ctrl+P first. It began to pay on a folder of archives sharing one password, and that is the case it served worst, still asking for two clicks per archive. The comment describing it promised behaviour the code did not have -- "one password to try before asking", when nothing ever tried it -- and pointed at a default_password_window that exists nowhere in the tree. Removing rather than finishing, because finishing it properly is the four pieces WinRAR already has: per-archive masks, an opt-in "accept without confirmation", a master password to earn the right to persist, and labels. That is its own piece of work, and it is written down in docs/todos/gestor-de-contrasenas.md along with what was taken out, so it can be rebuilt without archaeology. Migration: Ctrl+P and the Default password entry in the overflow menu are gone, as are the two "Use the default password" buttons. Passwords are still asked for and still work exactly as before, one archive at a time. Nothing was persisted, so nothing is left behind to clean up.
The box that asks for a password answers three different questions, and its button carried one label for all of them but the first: "Start". An archive that says "This archive is encrypted / Type the password to open it" then offered Empezar, which starts nothing. Now each branch says what pressing it does. Setting a new password keeps "Set password". Opening or extracting says "Open", reusing the word the toolbar already uses. The third case is the one the single label hid worst: when the password being asked for is the one the archive already has, pressing this does not open anything either, it brings up the second half of the question, so it says "Continue".
The old PKWARE cipher was rejected outright, so an archive from any tool that still writes it was a dead end: the listing opened and no entry came out. It is read now, with the same streaming shape as the AES path, and still never written -- anything Arca produces is AES-256. Changing the password of one of these archives therefore moves it to AES-256, which is the way out of the legacy scheme rather than a way to stay in it. The scheme carries a one byte password check, so one wrong password in 256 gets past it and fails on the checksum instead.
Opening an encrypted archive asked for the password and then believed whatever was typed: the listing is in the clear, so nothing contradicted it until an entry was extracted, and by then the box was long gone. The password is checked against the archive as it is typed, and a wrong one leaves the box open saying so. It reads only the few bytes at the front of the first encrypted entry, so it costs nothing and blocks nothing: two verifier bytes for AES, one for ZipCrypto. Extraction still has the last word.
The Windows build said `--target x86_64-pc-windows-msvc`, which is the host triple of the runner and bought nothing, but it moved the artifacts to `target/<triple>/release`, where the cache does not keep them. The job restored 613 MB of cache and then rebuilt all 381 crates anyway: seven and a half minutes on every run, against eight crates and one minute on Linux with the same cache hit. Dropping the flag leaves one build step for the three platforms. `windows/arca-shell` is outside the workspace and has a target directory of its own, which the cache was not told about, so the `windows` crate was rebuilt every run too. Its lockfile goes in with it: without one the resolution -- and with it the cache key -- changes on its own.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrate
arca-guifrom egui/eframe to GPUI and redesign the GPUI surface on top ofGPUI Kit. This covers phases 1 through 7 of the plan
added in
docs/plans/migration-to-gpui.md.Intentionally a draft: the GPUI window is already standing and looks good,
but there are still things to polish and platform validation to complete. The
list is below, under “What remains”.
Why
arca-guiwas a ~4,600-line window insrc/main.rswith business logic andegui rendering interleaved. The goal is not just to change toolkits, but to
keep state out of the type that the toolkit renders.
What changes, layer by layer
1. State is separated from the toolkit
An
AppControllernow owns the inputs, current folder, history, selection,filter, sort order, jobs, and pending dialogs. It is driven through
AppActioninstead ofegui::Context.The workers and message channel are unchanged: no disk or compression
operation touches the UI thread. That is precisely what allows two possible
windows to share the same logic, and what makes this PR incremental rather
than a big bang.
2. GPUI comes from GPUI Kit, not a Zed revision
The original plan pinned
zed-industries/zed@3384317. That has been removed:gpui-componentis built againstgpui-pre ^0.3.which do not link together.
The solution is to rename the packages in
Cargo.toml, so not a singleusein the code changes:Resulting unified graph:
gpui-pre 0.3.4+gpui-base 0.6.0+gpui-component 0.6.0. Reproducibility comes fromCargo.lock, which is nolonger in
.gitignore— a pin that is not versioned is not a pin.spikes/gpuikeeps the original revision: it records what was validated inG1 and is not rewritten retroactively.
3. Monochrome light and dark theme (
arca-gui/src/gpui_theme.rs)Six greys per mode, neutral: no hue at all. Dark is painted on true black,
the way Geist paints a dark surface. Light is not that palette inverted by
formula — a light window needs its steps closer together or the chrome starts
to stripe.
background#000000#FFFFFFsurface#0A0A0A#FAFAFAraised#171717#F2F2F2border#2E2E2E#E0E0E0text#EDEDED#171717muted#A1A1A1#666666There is no accent color. Everything that would use one is the text color
at a fraction of its opacity: selected row 12%, hover 5%, alternating stripe
2%, focus ring 70%, text selection 18%. Contrast is guaranteed by construction
rather than by a table someone has to maintain: if the text is readable, the
selection is visible.
The only exceptions are
dangerandwarning. They are not decoration — theydistinguish “extracted” from “not extracted”, and anyone scanning before
reading needs to be able to see that distinction. Dark uses
#E5484Dand#F5A623; light darkens them to#C50E1Fand#A15C00, because the red thatcarries on black does not carry on white. A test holds
dangerat 4:1 againstits own background.
The tokens are the ones from
gpui-component; Arca does not add its owntoken layer, it only specifies the value of each token.
Radius 4/6 px, base font size 16 px, monospace one step down at 13 px.
4. Layout redesign
The reference is Nohrs (the same problem:
a file explorer) with the density of DBFlux.
Before: a stack of floating strips in
p_3+gap_3. Now: full-bleed regionsseparated by 1 px lines, with padding inside each bar so every divider spans
the full width.
gpui_component::sidebar). It is content,not chrome: until now, reaching a deeply nested file meant double-clicking
down and navigating back up. The current folder branch opens automatically;
the rest stays closed. The tree is cached by (file path, entry count) —
rebuilding it in
rendermeant walking all names 60 times per second duringextraction.
gpui_component::status_bar), withthe summary on the left and counters on the right. The counters used to live
in the middle of the navigation row, pushing the breadcrumbs against the
right edge.
Seven outlined boxes read as seven competing things.
IconName, viagpui-kit-assets) instead of‹ › ↑.half the window down.
and alternating stripes.
5. Accessibility
What was already there is preserved and not downgraded: AccessKit roles,
visible focus, keyboard navigation, focus trapping in modals, and the
background subtree without roles while an overlay has focus. Icon buttons keep
their accessible names as words.
Verification
cargo test --workspace— green (default build, egui).cargo test -p arca-gui --features gpui— 38/38.cargo fmt -p arca-gui -- --check— clean.table, status bar, dark theme.
Two new tests in
gpui_theme.rs:both modes. With no accent, the palette is the accessibility, so this is
the test that matters.
that stops being the inverse of the other. These are copy-and-paste slips that
compile and do not show up in a diff of thirty hex literals.
I wrote a third test that failed (it assumed the three backgrounds are ordered
in the same direction in both modes). That is false: in light mode a panel is
raised toward white and a hover is tinted away from it. Rather than tuning
the threshold until it passed, I replaced it with the thing that can actually
break.
What remains
That is why this is a draft.
the ones from
gpui-component:Input— removes the roughly 400 lines ofFilterInputand its UTF-16IME contract. This is the largest pending deletion.
Modal/Root,Popover,Table,Notification.absoluteand fixed offsets (top(38.) right(232.)),rather than being anchored to their trigger. They break if the filter width
or font size changes. The correct solution is
Popover.theme and language to be changed without editing
gui.conf. For now thepreference is read at startup and
Systemfollows the desktop.and macOS are still unmarked in
docs/plans/gpui-spike-baseline.md, andthey should not be marked without a real test.
The plan says any accessibility limitation blocks removing egui, so this is
required before phase 8.
eframe,egui,egui_extras) is not done, and should not be done until 4 and 5 are closed.Notes for reviewers
multiline
dialog_buttoncalls with a bad regex. Theon_clickbodiessurvived intact, and
modal_focus_targets/modal_enterpin the exact id andfocus handle of each button, so the reconstruction is deterministic except
for the visible text of three:
"Set Password"/"Unlock","Keep Both"/"Keep Both Always", and"Delete". They deserve a look.are not split further because any intermediate cut inside
gpui_shell.rsproduces a commit that does not compile.
Settings::loadalready reads thecolumnskey, which was a parity defectnoted in G1. The
gui.confformat does not change.