Changelog
Cosa c'è di nuovo.
Ogni miglioramento alla piattaforma, come si spedisce.
· kamo-internal
Pugnala la cascata, aggiungi azioni di rinfuse di ramo, spiega la regola
La cascata evidenzia ora viaggia la catena invece di lampeggiare ogni interessato fila subito. abilitatoAncestori già arriva più vicino, quindi l'indice è distanza viaggiato su l'albero e ogni fila ritarda di 90ms dietro l'ultimo — guardare esso spostare il bambino a genitore a nonno è ciò che insegna la regola, che un flash simultaneo legge come un glitch. Ancora istantaneo sotto la preferenza-ridotta-mozione. Concedi tutto / Rimuovi tutto su un ramo, mostrato solo quando cambierebbero qualcosa. Percorso tramite app.Toggle per destra piuttosto che scrivere le impostazioni direttamente: a l'azione di massa non deve diventare una seconda implementazione delle regole di cascata che deriva dal singolo in dirittiHierarchy.ts. La pillola concessa/totale è ora sempre su una fila genitore piuttosto che solo quando collasso — è la lettura più veloce di ciò che un ramo realmente concede. Rende anche gerarchiaHeading / gerarchiaBody, che il commit precedente aggiunto a il dizionario e poi mai usato. Mostrato solo sopra un gruppo che in realtà ha nidificare; un banner che appare ovunque è che una gente impara a saltare. dirittiTreeForest.test.ts pins la regola di ancoraggio l'albero e i rami-tipi editor share: un diritto il cui genitore è filtrato dal gruppo corrente allega al più vicino antenato PRESENT, o diventa una radice. L'altra strada fa un concessione permesso svanire dal redattore con nulla per indicarlo — e esso è raggiungibile in pratica, perché EDIT QUOTE TERMS è dichiarato CRM mentre la sua genitore MANAGE QUOTES è POS.
· kamo-internal
Mostra e applica la gerarchia dei permessi in tutti e cinque gli editor
Cinque editori hanno concesso 199 diritti come cinque liste piatte di caselle di controllo, e nessuno dei hanno mostrato che un diritto può essere un prerequisito per un altro. Concedere un bambino senza il suo genitore sembrava esattamente come concedere un permesso di lavoro — che è come la scheda Legal & Compliance aveva bisogno di due diritti e nessun editor poteva dirlo. Diritti Albero rende l'albero: indentazione con una spina dorsale del connettore, pieghevole rami, una pillola / totale per ramo, ricerca che mantiene gli antenati di una partita visibile, e una cascata che è animata perché l'animazione è ciò che insegna regola — permettere a un bambino di accendere ciò che dipende, disabilitando un genitore dims suo subtree, entrambi con un brindisi che non ha dato il nome esattamente quello che si è mosso. Onori Preferisce-ridotta-mozione. Inoltre smette di mentire su tre stati che gli editori non potrebbero mai esprimere: i diritti bloccato dal modello di sicurezza dell'org, file ereditarie concesse-but-inert il cui genitore è negato, e i 58 diritti che sono concessi, ma non cancello nulla ovunque. Un amministratore in precedenza non poteva dire un vero permesso da un segnaposto. Le regole della cascata vivono in app/lib/rightsHierarchy.ts, specchiato da Java RoleRightType. Una copia di una regola di sicurezza è una responsabilità, quindi il test di parità legge la fonte Java e fallisce su qualsiasi divergenza piuttosto che convenzione di fiducia. L'editor di modelli di ramifica ottiene l'ordine e l'indentazione, ma mantiene la sua proprio controllo e NON auto-cascade: sono valori FORCE, assoluti e applicato dopo ogni livello, in modo da cascading loro avrebbe silenziosamente riscrivere un deliberato override. La relazione dei genitori è ancora applicata al momento della risoluzione. Design + audit riproducibile: docs/superpowers/specs/
· SecurityService
Normalizzare il membro sovrascrive contro la gerarchia
I diritti diretti dei membri passano ora attraverso RightsHierarchy. normalizzareLivel prima di loro sono perseverati: un bambino concesso tira i suoi antenati fino a GRANTED al membro livello, un genitore negato spinge NUKE giù il suo sottotetto lì. lato server piuttosto che fidarsi dell'editor, perché questo endpoint è raggiungibile direttamente. Un livello memorizzato con un bambino ma non il suo genitore è esattamente quello che più tardi le strisce di chiusura del risolutore — il membro continuerebbe a perdere un diritto il L'UI ha mostrato come scontato, con nulla da spiegare. Raccogliuto in una mappa prima così la gerarchia vede l'intero livello subito; normalizzare l'ingresso per voce dipenderebbe dall'ordine che il cliente è accaduto di inviare.
· InitializerService
Antenati di riempimento in modo da la chiusura non può rimuovere l'accesso
Il servizio parlamentare nega ora qualsiasi diritto la cui catena madre non è concesso. Ogni riga scritta prima che esistesse la gerarchia non sa nulla di antenati, quindi un ruolo che detiene MANAGE SUBSCRIPTION PLANS senza ACCESS COMMERCE — tutto normale fino ad ora — lo perderebbe sul successivo ricalcolo. Dall'altra parte 13 organizzazioni dal vivo che è un outage, non una migrazione. Porta tutti e quattro i livelli (principi, reparti, titoli di lavoro, override dei membri) all'invariante i percorsi di scrittura ora mantengono, utilizzando la stessa regola: un dato discendente tira i suoi antenati fino a GRANTED. Passo 2 è quello che in realtà previene l'estrazione — capovolge un antenato esplicitamente DENIED da concedere, perché righe che esistono sopravvivono passo 1 intoccato e la chiusura sarebbe poi revocare ogni discendente sotto di loro. Modelli di sicurezza non sono volutamente toccati: un FORCE NUKE su un genitore è un amministratore che spegne un subtreo di proposito, e la chiusura onorare che è la caratteristica. Passo 3 è un GATE, non una linea di log: ricontrolla l'invariante e fallisce l'esecuzione se qualsiasi livello detiene ancora un discendente concesso sotto un antenato non finanziato. A migrazione che ha segnalato il successo con una violazione lasciata alle spalle avrebbe consegnato problema al risolutore, dove si esegue come membro tranquillamente perdendo un il permesso con nulla per spiegarlo.
· kamo-shared-library
Gerarchia dei genitori/bambini per tutti i diritti 199
RoleRightType ha dichiarato un campo 'parent' e un pubblico ottenereParent() per anni. Ogni costante passava nullo e niente lo chiamava. Questo finisce che ponteggio: 135 bambini, 34 radici, 30 standalone, massima profondità 3, derivata da un audit di tutti i 199 diritti oltre 15.081 file sorgente attraverso i 40 repos. Punti di forza TWO, e la divisione è il disegno: normalzeLevel() è AUTHORING. Rende un livello di autocoerenza — un dato bambino tira i suoi antenati a quel livello, un genitore negato spinge NUKE giù il suo sottotetto lì. Applicato nel ruolo, il dipartimento e il lavoro-titolo percorsi di scrittura. Poiché ogni livello rimane coerente, un titolo di lavoro che concede un bambino concede anche sovvenzioni il genitore THERE, e la cascata esistente già permette il più specifico Il livello ha battuto un dipartimento che l'ha negato. Nessuna regola di precedenza cross-level era aggiunto e la cascata è intatta. ApplicareChiusura() è RISOLUZIONE, allegato dopo l'applicazioneTemplateRules. È un rete di sicurezza per le uniche due cose che l'autore non può raggiungere: un modello di sicurezza FORCE NUKE su un genitore (assoluto, applicato ultimo, quindi senza questo un forzato-off il genitore lascia i suoi figli concessi) e le righe scritte prima di tutto questo. Due coppie rimangono deliberatamente FLAT perché la separazione dei doveri dipende da esso: APPROVAZIONE TIMECARD ADJUSTMENTS / FINALIZE PAY PERIODS, e APPROVE ORG PAYOUTS come un fratello di MANAGE ORG FINANCES piuttosto che il suo bambino — nidificare quest'ultimo forzare ogni pagamento approvatore di essere anche un emittente. Design: kamo-internal E' il momento giusto
· kamo-internal
Riparare il promemoria nel colore dell'org, aprire le sue opzioni in posizione
La carta è il colore primario dell'organizzazione ora, con ogni segno disegnato in --su-primario piuttosto che un bianco letterale — un bianco-label org che sceglie pallido primario può ridichiarare che un token e spostare l'intera carta. Chips, ringhiere e i bordi sono mescolati a colori dallo stesso token in modo che rimangano leggibili in entrambi i modi. L'azione primaria è una pillola invertita, corrispondente all'idioma la firma legale eroe utilizza già per un pulsante su una banda colorata. La colonna vertebrale per-kind accento è andato; l'icona porta già il tipo, e un il secondo colore su una carta colorata l'ha solo morso. L'eccedenza è l'eccezione, e invertisce piuttosto che girare rosso: il rosso su un colore org arbitrario è illeggibile. I suoi menu Snooze e More erano menu MUI, che porta al documento e portano un blocco di scorrimento con loro. La serratura imposta il troppopieno: nascosto e compensato imbottitura-destra su document.body, e imbottitura del corpo muove ogni posizione fissa elemento sulla pagina — così il mazzo, che è fissato in basso/destra 20, saltato il momento in cui qualcuno ha raggiunto per Snooze, e il menu ha coperto la carta apparteneva. Entrambi sono un pannello in linea ora, che si espande all'interno della scheda con la attivare a sinistra acceso. Nessun portale, nessuna serratura a scorrimento, niente da coprire.
· kamo-internal
Impostazioni legali e di conformità scheda con il testo legale predefinito org
Una nuova scheda su /settings/features/hrs che tiene il testo legale predefinito, collegato dal menu delle impostazioni e la finestra di lancio delle impostazioni. Creazione di un pacchetto su /hr/legal-compliance ora parte da esso, e quel campo è tre file più alto. Dritti. La pagina delle impostazioni HRS nel suo complesso ora REFUSE TO RENDER senza MANAGE HRS SETTINGS piuttosto che solo reindirizzare: il reindirizzamento è un effetto, quindi non può funzionare fino a dopo un rendering, e ogni scheda era la pittura — e sparare la sua carichi di tempo di montaggio — per un membro che non tiene la destra. La nuova scheda ha inoltre bisogno di MANAGE LEGAL AND COMPLIANCE, tenuto identico in tre posti (pagina, link menu, scheda di lancio) e pinned da un test, perché un link offerto a qualcuno la pagina non renderà la scheda per aprire Provider Setup invece: SettingsView risolve un ?tab= sconosciuto indice 0 piuttosto che errore. La finestra di dialogo semi il default solo mentre l'autore non ha digitato — il fetch si risolve dopo che potrebbero già essere scrivere. semeDefaultAttestation è un puro helper in modo che la regola sia testabile, e viene applicato OUTSIDE l'aggiornamento dello stato: Reagisci con gli aggiornamenti di doppio invoca nello sviluppo, che ha lasciato l'istantanea di seme avanzato e la seconda chiamata che rifiuta il proprio cambiamento. Un EDIT non torna mai. Il pacchetto porta la propria formulazione e sovrascrittura riscriverebbe a quale pubblico di una versione pubblicata viene chiesto di accettare.
· kamo-internal
Testo ricco nel appiccicoso e le anteprime, e una modalità è possibile commutare
Una nota ora dice se è testo ricco o testo normale, e sia l'editor e il galleggiante appiccicoso può commutarlo. La nota galleggiante in modalità ricca incorpora lo stesso editor /notes utilizza, quindi ha ogni strumento che la pagina intera ha — vestito nel colore di quella nota attraverso un nuovo `tono` opzionale prop su Kb ArticleEditor. Il prop è additivo: assente, l'editor sembra esattamente come ha fatto, quindi la conoscenza la base è intatta. La barra degli strumenti avvolge e scorre piuttosto che spingere la la zona di scrittura fuori una stretta appiccicosa. Interruttore ricco -> chiede in primo luogo, e dice chiaramente che la formattazione viene scartato e non può essere recuperato mentre il testo viene mantenuto. Non è nascosto — la modalità normale non ha alcun posto per metterlo — quindi la conversione anche istantanee una versione, lasciando la vecchia formattazione recuperabile dalla storia. Plain -> ricco basta accendere gli strumenti e non ha bisogno di avvertimento. Le anteprime sulle carte da parete e i chip del launchpad ora mostrano il reale contenuto piuttosto che una linea appiattita. Essi rendono attraverso un standalone lexicalToHtml piuttosto che un'istanza Lexical montata: una parete è decine di le carte, e ogni altrimenti trascinano nell'intero runtime dell'editor visualizza un paragrafo. Tale output viene iniettato, e il contenuto di nota è membro autorizzato e spesso incollato, quindi è la parte sensibile alla sicurezza ed è stato scritto test-first — escaping, uno schema consentista che rifiuta javascript: e dati: (compreso scheda e caso-obfuscato), citazione-escaping dentro href, e le note raw-HTML legacy mostrate come testo piuttosto che reso. Mentre una ricerca è in esecuzione le carte mantenere la semplice anteprima evidenziata: marcare il colpo conta più metà ricerca, e non può evidenziare reso HTML senza riparlare quello che era appena serializzato. Inoltre: le note galleggianti portano lo sfondo configurabile dell'org filigrana, la stessa che usano le pagine dei contenuti, onorando l'org bgWatermark impostazione compreso NONE. Dimensione come una parte della nota in modo che tiene a qualsiasi dimensione, e un po 'più forte di quello che le pagine utilizzano perché un sticky si siede su carta saturata piuttosto che vicino-bianco.
· KBService
Effettuare una nota ricco-testo / modalità di testo semplice attraverso l'API
Crea onori la modalità richiesta e i default a ricchi, abbinando editor una nota è creata in. L'aggiornamento è nullo come ogni altro campo su quel punto finale, quindi un chiamante che invia solo { isRichText } non fa svuotare la nota. Il DTO legge attraverso isRichTextOrDefault(), quindi una riga che in qualche modo fuoriuscito il riempimento è segnalato come ricco. Erring verso il mantenimento la formattazione è la direzione sicura: l'opposto presenterebbe un formattato Notare come semplice e invitare il membro ad appiattirlo.
· InitializerService
Aggiungi la colonna is rich text, predefinita e riempita di backup a ricchi
Se una nota viene modificata come testo ricco o testo normale non può essere rileggeta dal suo contenuto: una nota ricca che nessuno ha formattato ma è byte-for-byte una semplice. Senza una colonna l'editor perde la sua barra degli strumenti ogni volta che la la nota viene riaperto. TRUE predefinito, che è anche ciò che consente a YugabyteDB di aggiungere la colonna a una tavolo che ha già righe. TRUE specificamente perché ogni nota scritta prima che la colonna esistesse è stata autorizzata nel ricco editor — era il solo l'editor c'era — così il backup a FALSE mostra formattato note semplici e offrono di appiattirli. @Order(0) per il solito motivo: il momento condiviso-lib mappa la colonna, ogni corridore che carica una nota lo SELECTs, quindi una migrazione tardiva non è mai raggiunto. Navi con la guardia alla deriva che questo pacchetto utilizza — riflette la colonna nome fuori l'entità piuttosto che riaffermare esso, ed è stato controllato rompendo il nome e guardarlo falliscono. Applicato contro Yugabyte: colonna presente come boolean default vero, tutto righe esistenti riempite a true, no NULLs.
· kamo-shared-library
Una nota riporta se è testo ricco o testo normale
La modalità non può essere derivata dal contenuto. Una nota ricca che il membro ha non formattato ma serializza byte-for-byte identico a quello normale — entrambi sono un documento Lexical di paragrafi non progettati — quindi senza da qualche parte per tenerlo, l'editor perde la sua barra degli strumenti ogni volta che la nota viene riaperto. Predefiniti a ricchi, e la colonna è riempita a TRUE, perché ogni nota che preda la bandiera fu autore del ricco editore; era l'unico uno c'era. Il backup a FALSE presenterebbe note formattate come semplici e invitare il membro ad appiattirli. Nullable di proposito, con isRichTextOrDefault() leggendo una bandiera non impostata come ricco: una fila che in qualche modo sfugge al backfill dovrebbe err verso mantenere formattazione, non verso scartarlo.
· kamo-internal
Una pagina per le notifiche e i controlli su di loro
La pagina delle notifiche indossava un'intestazione propria — un sopracciglio, un titolo e tre pulsanti di pillola all'interno di una carta di confine — mentre ogni pagina vicina (il conto browser, ogni schermata delle impostazioni) viene introdotto dall'eroe gradiente di marca con un freccia posteriore e una barra di linguetta sotto di esso. Si legge come altrove nel prodotto. Si'. ora porta lo stesso intestazione, con il conte non letto come il suo sottotitolo e i due azioni che operano sulla lista come pulsanti bianchi dell'eroe. Sotto di esso, due schede: Notifiche e Impostazioni. Le impostazioni sono quelle che usato per essere una scheda sul proprio profilo del membro a /settings/member/{id}?tab= notifiche, che mettono i controlli sulle notifiche su una schermata diversa dalle notifiche stesse — un soggetto in due luoghi, nessuno dei quali ha menzionato l'altro. Un membro che apre la campana perché non si fermerà è ora già sulla pagina che può fermarlo. `?tab=` è condiviso attraverso NOTIFICATION TABS, quindi la barra e le superfici che si collegano a fondo non possono alla deriva su un valore che non seleziona nulla. Entrambe le superfici collegate alla vecchia posizione (l'azione Impostazioni del ponte tostato, il pulsante Impostazioni della pagina, ora una scheda) vai a NOTIFICATION SETTINGS HREF. An vecchio segnalibro dell'URL membro si risolve ancora — ImpostazioniView ritorna al prima scheda per un `?tab=` non riconosce. La copia Quick Tips che ha staccato la vecchia scheda (settings.notifications.help.*) è non portato sopra: il cassetto dell'aiuto è una convenienza di SettingsView e questa pagina ha Nessuno. Le chiavi sono lasciate nel dizionario piuttosto che strofinare.
· DocsService
GET/PATCH il testo legale predefinito dell'org
/api/docs/legal/settings, dietro lo stesso imbuto sorvegliato() come ogni altro mapping su questo controller. La lettura non crea mai una riga: si risolve a LegalDefaults quando l'org ha Nessuno. Che mantiene libero l'editor di seme del pacchetto fetch effetto collaterale ed è ciò che copre le organizzazioni create dopo la corsa del seme di KamoInitializer — la fila è materializzato sul primo salvataggio, dove una scrittura appartiene. Il set giusto è deliberatamente più ampio degli endpoint del pacchetto. Questo punto è raggiunto dalla scheda Impostazioni Legali & Compliance (MANAGE HRS SETTINGS) e dall'editor del pacchetto (MANAGE LEGAL AND COMPLIANCE); da soli 403 uno schermo su un campo che possiede. Sicuro da allargare perché è una stringa di livello org di caldaia — nessun roster, nessun incarico, nessuna prova — e un test di regressione perni che l'ampliamento non raggiunge elenco/creare/audience-preview. Semantica PATCH: un campo assente è intatto, una stringa vuota è deliberata Via libera. Publish già rifiuta un'attestazione vuota, quindi un default cancellato solo rende l'autore scrivere il proprio.
· InitializerService
Seed ad ogni organizzazione un testo legale predefinito
Una riga hr legal settings per org, unica su org id, che trasporta E' il momento giusto. INSERT-ONLY, mai un UPDATE. Un org che ha modificato la propria formulazione la possiede; a redeploy silenziosamente riscrivendo la frase la sua gente viene chiesto legalmente concordare sarebbe il peggior comportamento possibile per questa stringa. Questo rende anche il corridore idempotent. Orgs creati AFTER questo funziona sono coperti dal DocsService leggere fallback piuttosto che da org provisioning, quindi un futuro percorso di provisioning che ha dimenticato call non può spedire un org il cui testo legale predefinito è vuoto. Entrambe le dichiarazioni retry 40001: un tiro da un CommandLineRunner interrompe l'intero inizializzatore e salti ogni corridore ordinato dopo di esso.
· kamo-shared-library
Org-level predefinito testo legale
LegalSettings è una riga per organizzazione che trasporta la formulazione di un NUOVO legale il pacchetto inizia con. È un DEFAULT, mai un riferimento dal vivo: creare un il pacchetto copia il testo e la pubblicazione blocca che copia su una versione, quindi modificarlo qui non può modificare un pacchetto che esiste o qualcuno ha già d'accordo. LegalDefaults detiene la formulazione della piattaforma in un posto. Il KamoInitializer seme, il DocsService leggere fallback e il campo delle impostazioni tutti legano ad esso — tre literali andrebbero alla deriva e og a lato della migrazione sarebbe allora non sono d'accordo su cosa sia il default.
· kamo-internal
La storia diventa una pagina, e la campana suona
La campana ha aperto un popover, che è la forma giusta per "cosa è appena successo" e quello sbagliato per "che cosa ha detto quel promemoria martedì". Un pannello appeso a un la barra degli strumenti non può essere paginata, non può essere filtrata senza affollamento, e si chiude quando si cliccare su qualcosa — in modo che un membro possa vedere alcune recenti notifiche non lette e nient'altro. Tutto già letto era, in pratica, andato. Questo è un povero proprietà per la registrazione di ciò che l'applicazione ha detto a qualcuno. /notifiche è quel record. Si legge come una cronologia piuttosto che una lista, perché le notifiche sono eventi e la domanda che un membro porta è quasi sempre "quando": una spina dorsale di giorni con le notifiche pendono, ogni punto nel suo genere di colore, quindi un mese di storia legge come bande prima di un singolo si legge. Le file non lette portano il terreno e quelle leggere portano solo il punto, che è la differenza che viene scansionata. La barra del filtro restringe ciò che il la spina dorsale tiene piuttosto che riordinarlo — letto, illeggibile o entrambi, e per genere con un conteggio contro ciascuno. Il raggruppamento di giorno è il calendario-consapevole piuttosto che l'orario trascorso: a metà mezzanotte, è successo qualcosa da quattro ore fa ieri, e "4h fa" non è che cosa una persona alla ricerca di esso esegue la scansione. Tutto ciò che il server non poteva datare è tenuto e raggruppato, dove non può rivendicare un giorno a cui non appartiene. La campana è ora un collegamento, segnato come gli altri due segni di attenzione nel bar quindi tutti e tre leggono lo stesso modo. Ciò che differisce è il movimento, e deliberatamente: un documento eccezionale e un orologio in esecuzione sono stati, e pulsano un anello che dice "ancora vero". Una notifica non letta è un evento, e che cosa fa una campana quando un evento accade è anello. Così oscilla — colpito una volta e settling, ampiezza halving 12° → 9° → 6° → 3° → 1,5°, pivottanti alla corona perché una campana pende — e poi riposa per quattro secondi. Una campana che non si è mai fermata sarebbe un allarme, e il conteggio accanto a esso già dice la cosa persiste.
· EmailService
Una storia pagina, filtrabile
Il centro potrebbe rispondere ad una domanda: le più recenti, illeggi o no, senza alcun modo di chiedere quello che era già stato letto. Così tutto ciò che un membro aveva visto era, in pratica, andato — che è una proprietà povera per la registrazione di ciò che L'applicazione ha detto a qualcuno. GET /api/notificazioni/pagine browse, riporta il totale, conta ogni tipo per un filtro ferroviario, e prende uno stato di tutti, non letto o letto. L'elenco esistente rimane come è stato: riempie il ponte vivo, che vuole i più recenti pochi e nient'altro. Costruito da predicati piuttosto che una query per combinazione di filtri, e Non da (:kind IS NULL O ...) — un parametro null non digitato è quello che ha fatto ogni lookup del calendario 500, e un predicato omesso non può farlo.
· kamo-shared-library
Lascia che la storia sia interrogata dalle specifiche
I filtri di storia sopraccigliabili per genere e se una notifica è stata leggere, e quelli combinano liberamente — che come JPQL è una query per combinazione, o una domanda piena di (: genere IS NULL O n.kind = :kind). Il secondo è quello che ha reso ogni lookup del calendario risposta 500: un parametro null arriva non digitato e PostgreSQL rifiuta la dichiarazione in modo definitivo. Una specifica omette il predicato piuttosto che passare un nullo, quindi la colpa non può ripetersi qui.
· kamo-internal
Ricostruisci /nota come una parete di carta con un editor split-pane
La pagina era l'ultima schermata ancora sul crudo Tailwind blue-600/gray-50 mentre tutto intorno si è trasferito al sistema di casa, e ogni suo ~100 `dark:` varianti era codice morto — globals.css pins `color-scheme: light`. Ha anche ignorato `--accent-knowledge`, il token accento riservato per esattamente Questo modulo. Ricostruito intorno alla metafora i dati già trasportati: la tavolozza è post-it per costruzione, così le note rendono come carta — altezze di muratura, un inclinazione deterministica per-id che raddrizza su hover, una testa di spillo su pinned note nel profondo della nota. Una nota non colorata ottiene carta normale piuttosto che risolvereStickyColor()'s limone fallback, che altrimenti dipingere l'intera parete giallo e passare la tavolozza prima del membro scelto Qualsiasi cosa. Le bande seguono il tipo attivo invece di essere fissi, quindi un divisore solo costa spazio quando dice qualcosa di vero: recency under a date sort, iniziali sotto una sorta di titolo, open-frequency sotto una sorta di view-count. Aggiunge anche: una tavolozza di comando (⌘K), una barra di filtro pieghevole con colore campionatura e macchie di parete, multi-seletto con azione a turni e di massa, tastiera nav (/ ⌘K N P F X frecce), evidenziazione della ricerca, caricamento dello scheletro nella forma del muro, e separato vuote vs no-risultati stati. L'editor ora si apre accanto alla parete su un divisore trascinabile piuttosto che sostituendo la pagina, ed è contrassegnato da note id in modo che le note di commutazione lo reimposta. Filtraggio, smistamento, fasciatura, bilanciamento della muratura e geometria della carta si muovono app/lib/noteWall.ts dietro 48 test unità. Contenuto basato su DOM di NoteCard parser è andato — ha usato documento. creareElement, quindi una nota la cui contentPlain è stato annullato su salva non ha reso alcuna anteprima a tutti i server-side. Rimuove NOTE SORT OPTIONS, che ha spedito etichette di tipo inglese hard-coded in tutte le 22 locali.
· kamo-internal
Pin e unpin una nota dal suo galleggiante appiccicoso
L'attaccante potrebbe ricolorare, rinominare, minimizzare, massimizzare e chiudere una nota ma non pin esso — così pinning significato tornare a /notes per l'unica cosa nota è già davanti a voi per. Pinning è una proprietà della nota piuttosto che del appiccicoso, quindi il gioco scrive direttamente alla nota e torna attraverso la cache condivisa. Questo è che cosa lo fa atterrare ovunque in una volta: la lista /notes, e le note a spillo band sulla home launchpad (dove rimane nascosto fino a quando il sticky è chiuso, come prima). Ottimistico, e mette il perno indietro se la scrittura fallisce. Riutilizza componenti.notes.card.pin / .unpin e la stessa icona dell'editor disegna, quindi un perno significa una cosa su tutte e tre le superfici. Niente nuove corde.
· kamo-internal
Una finestra di ore tranquille, che regola entrambi i canali di notifica
C'erano due. La coppia vicino alla parte superiore della pagina ha messo a tacere le carte in-app; la coppia in fondo ha messo a tacere le e-mail mancate-messaggio. Niente sullo schermo ha detto erano cose diverse, quindi un membro che imposta le ore tranquille ha dovuto notare il secondo set esisteva e lo impostava troppo — e l'ipotesi ragionevole, che quello avevano già riempito in coperto la sera, era sbagliato. La finestra in cima è ora l'unico, e scrive entrambi. Sono memorizzati di servizi diversi — ore in-app sulle preferenze di notifica del membro, ore di e-mail sulla ui-preference MediaService legge — così il controllo scrive due volte, che è esattamente l'impianto idraulico di un membro non dovrebbe mai sapere. I due negozi sono riconciliati una volta, a carico, perché un membro può arrivare con un finestra da un lato e non dall'altro. Qualunque sia il set vince: una finestra e-mail è adottato quando il lato in-app non ha nessuno, quindi niente configurato viene gettato e altrimenti la finestra in-app viene spinta verso l'esterno in modo che il controllo unico su schermo sta dicendo la verità su entrambi i canali. La preferenza email è di proprietà del pannello ora piuttosto che dalla sua sezione. Due componenti debouncing scrive alla stessa chiave memorizzata correrebbe e potrebbe persiste mezza forma, e MediaService legge un campo assente come predefinito il membro non ha mai scelto.
· kamo-internal
Due significati del 100% — una larghezza di lettura, e l'intero pannello
La percentuale si resetta ad una linea di base: la pagina coltivata al pannello ma bloccata a 920px, che su un monitor largo lascia il foglio galleggiante in un sacco scrivania vuota. Il berretto è deliberato — una pagina di lettera allungata su un display da 34 pollici è un documento peggiore, non più grande, perché l'occhio non può tracciare una linea così lunga — ma non è sempre ciò che il membro vuole. Leggendo stampa fine o lavorando una forma densa, vogliono lo spazio che hanno. Quindi ci sono ora due, e un toggle nelle capsule sceglie che: pagina la larghezza leggibile, tappata a 920. A cosa si apre il pannello. larghezza riempie il riquadro, meno una grondaia 24px quindi l'ombra del foglio non è clipped contro il bordo e la pagina legge come stabilito piuttosto che storto. Sono un MODE, non due fermate più sulla scala di zoom, perché sono intenti diversi piuttosto che ingrandimenti diversi. Moltiplicazioni dello zoom qualsiasi linea di base è attiva, quindi la lettura rimane onesta su quello che è è un multiplo di, e + / − ancora lavorare da entrambi. Attivare la linea di base LANDS su di esso — lo zoom ritorna al 100%. "Fit to wide" che lasciato uno zoom del 250% applicato sulla parte superiore non si adattava a nulla, e il membro ha chiesto una larghezza, non un moltiplicatore di uno. La funzione di attivazione è un pulsante con `aria-pressed`, e i suoi nomi tooltip COSA PRESENTARE invece di quello che è attualmente in: "Fit to Width" in pagina modalità, "Reset view" in modalità larghezza. Entrambe le chiavi già esistevano e sono non-vuoto in tutte le 22 locali. maxPageWidthFor copre i bordi nei test: cade una misurazione del pannello NaN tornare al berretto leggibile piuttosto che raggiungere PdfPages come maxPageWidth e rendere nulla, e un pannello collasso non restituisce mai una larghezza troppo piccola per Leggi. Uno perni di prova il fatto che su un pannello più stretto del tappo i due le modalità convergono all'interno della grondaia — che è corretto piuttosto che un bug a design intorno.
· kamo-internal
Aggiorna l'orologio NavTop nel momento in cui l'editor salva
Svuotare il pugno che un membro è attualmente orologio sotto già li lascia OFF lato server: RosterService.clockState legge l'ultimo pugno LIVE e salti righe vuote, quindi nessun orologio è scritto e nessuno è necessario. Un SHIFT OUT qui sarebbe peggio di inutile — il SHIFT IN che si accoppia con era solo vuoto, così atterra come un orfano e solleva l'eccezione mancante-punch l'editor esiste per liberarsi. Quello che era effettivamente sbagliato era l'indicatore. Sonde su un piano 60s senza segnale dall'editor, quindi dopo la cancellazione della voce sono stati osservati sotto il membro ha guardato il cromo andare su dicendo che erano sull'orologio. L'editor ora annuncia un salvataggio che ha scritto qualcosa e il controllo rilegge. Fucilato da `finalmente` e portato su che cosa LANDED piuttosto che su successo: un salvataggio che ha fermato la metà strada ha cambiato ancora il record, e questo è il caso in cui Il distintivo stale è più difficile da notare. L'evento porta il membro modificatoId perché l'orologio NavTop è auto-scopio (fetchMyClockState non prende nessun membro id) mentre questo stesso editor viene utilizzato da HR contro altri membri a /hr/attenzione. dovrebbeRefreshClock Per i confronti come STRINGS e rifiuta un numero, per il motivo parseClockState dà già: ids sono valori unici rowid() oltre MAX SAFE INTEGER e due membri possono girare allo stesso numero. Non risolve il browser dell'altro membro quando l'HR modifica la loro carta — che ha bisogno il timecard.clock.
· kamo-internal
Zoom il documento e afferralo per spostarlo
Una capsula galleggiante nella parte inferiore destra del pannello del documento: −, la corrente percentuale, +. READOUT IS THE RESET — un pulsante di reset separato sarebbe un terzo controllo per qualcosa che il membro vuole solo una volta che possono già vedi che non sono al 100%, e dove guardano per controllare è il numero. È un vero pulsante con un'etichetta, così i lettori di tastiera e schermo lo raggiungono. 100% significa FIT-TO-PANE, non dimensioni reali. `zoom` moltiplica la larghezza di misura scala PdfPages già calcola, e la vista che il membro ha iniziato è quello significano quando preme "100%". I passi sono moltiplicativi (x1.25), non ±0.25: un passo fisso è un quarto della pagina al 100% e un sedicesimo di esso al 400%, quindi lo zoom indietro da lontano in ci vorrebbe una dozzina di presse. The maths è in app/lib/pdfZoom.ts con test, compreso che + poi − restituisce esattamente dove sei stato e che un NaN cade indietro per adattarsi piuttosto che raggiungere PdfPages come `scale = naturalScale * NaN`, che rende zero pagine e sembra esattamente come il documento che non riesce a caricare. DRAG TO PAN, ovunque non sia qualcosa che operi. usoDragToPan scrive scrollLeft/scrollTop piuttosto che trasformare il contenuto, in modo che eredita i limiti del contenitore, le barre di scorrimento e il comportamento della ruota gratuitamente — e l'asse orizzontale semplicemente non fa nulla fino a quando la pagina è più ampia di il riquadro, senza alcuna condizione per dirlo. Si rifiuta di iniziare input, textarea, select, button, a[href], label, [role=radiogroup] e riposo: sono le caselle di firma, i campi di testo e i gruppi di caselle di controllo InteractiveField rende, e i pulsanti della capsula di zoom. Il puntatore viene catturato lungo la strada verso il basso in modo da un trascinamento che lascia il pannello continua a tracciare e finisce ancora in modo pulito. MOUSE SOLO. Toccare già pentole nativamente con momentum e gomma-banding che nessun gestore qui può riprodurre, e il dirottamento che farebbe documento peggiore sui dispositivi in cui il trascinamento è il gesto naturale. Un layout fix viene fornito con questo: PdfPages centri pagine con `align-items: centro`, e un elemento flex più ampio del suo contenitore trabocca EQUALLY su entrambi i lati — la metà sinistra di cui nessuna posizione di scorrimento può raggiungere. Lo zoom oltre la larghezza del pannello avrebbe messo il margine sinistro di ogni pagina permanente fuori dalla portata. La pagina wrapper ora cresce al più ampio pagina (`larghezza: fit-content`, pavimento a `min-width: 100%`), quindi l'intero foglio rimane all'interno della zona pergamena.
· kamo-internal
Note incise sul launchpad, rotazione del colore e un corretto send-off
Tre cose. Le note incise raggiungono la home launchpad. Ogni nota incisa su /note ottiene un chip lì nel proprio colore appiccicoso, con una breve anteprima; fare clic su uno lo raccoglie sull'overlay, e un controllo dell'angolo lo apre in pieno editor invece per le cose che un appiccicoso non può fare. Una nota che è CURRENTLY FLOATING è lasciato fuori — è già sullo schermo del membro, e elencarlo offrirebbe la stessa nota due volte, la stessa regola che fa il suo /notes carta andare inerte. Chiudere l'adesivo rimette il chip. La band rende solo quando l'opzione note sopravvisse al cancello del nav, quindi non decide nulla sull'accesso da solo, e preferisce l'overlay copia cache di una nota in modo che un chip non mostri mai un titolo o un colore da prima E' stato preso. Nuove note ruotano attraverso la tavolozza. Ciascuno si apre sul colore dopo la nota più recente del membro CREATED, avvolgente alla fine, quindi un muro di bastoncini non è mai un blocco di giallo identico. Chiuso le note se stessi piuttosto che un contatore memorizzato, quindi rimane dritto attraverso i dispositivi e sessioni con niente di più da persistere; un set o non riconosciuto il colore precedente riavvia la rotazione invece di indovinare. Chiudere una nota ora lo calpesta. Un pizzico d'anticipazione, poi rimbalza, gira e scende via, lasciando un puff di scarti di carta nel proprio colore sopra lo spazio che ha lasciato. Gli scarti sono resi dallo strato, non dal nota: un bambino eredita la trasformazione dell'uscita della nota e si restringe con esso invece di scoppiare verso l'esterno, quindi la nota consegna il suo ultimo rettangolo e colore in uscita. Una nota massimizzata scoppia attraverso il l'intero viewport piuttosto che la dimensione che avrebbe ripristinato a; un minimizzato uno scoppia non affatto, non avendo spazio sullo schermo per scoppiare; e Preferisce-ridotta-mozione ottiene una dissolvenza normale senza scarti. Lo spargimento è un hash deterministico, non Math.random, che riscrive ogni rendering e jitter gli scarti a metà volo.
· kamo-internal
Impostazioni di notifica sulla larghezza, con un modello di lavoro
Era una colonna 760px di righe di commutazione, che legge come un modulo per riempire piuttosto che qualcosa da sintonizzare — e su un ampio schermo ha lasciato due terzi della pagina vuota mentre l'elenco dei tipi di notifica è andato fuori dal fondo. La vera domanda della pagina non è "ciò che sono le mie impostazioni" ma "quanto è difficile questo l'applicazione ha permesso di interrompermi", così ora si apre rispondendo che in frase assemblata dai valori live — nell'app, con un suono, sul tuo desktop, al di fuori delle ore tranquille — e dà la larghezza alle quattro decisioni dietro di esso: quali canali, dove atterrano, quando rimanere tranquillo, e che cosa è Ne vale la pena. Il centrotavola è una tappa: il proprio viewport dell'app in miniatura, dipinto nel i gettoni scuri del mazzo di notifica mentre la pagina intorno a esso rimane leggera, perché che è il contrasto esatto una notifica reale fa quando arriva sopra un schermo di lavoro. L'angolo è scelto facendo clic sull'angolo e sulla carta trasversa piuttosto che tagliare, quindi il controllo e la sua conseguenza sono una gesto. Quattro quadranti hanno battuto una discesa qui: la scelta è spaziale, e parole "basso destro" sono una mappa peggiore di uno schermo che una foto di uno. Accanto a esso, Mostrami alza una vera notifica nel ponte reale al membro proprio suono, angolo e tempo di licenziamento. Una pagina delle impostazioni per le notifiche dovrebbe essere in grado di mostrare una notifica, e un mock sarebbe una foto di uno — quindi previewToast mette una carta di lancio attraverso il ponte reale, scrivendo nulla, non pubblicare nulla, e non raggiungere mai il centro. I tipi sono diventati piastrelle dove l'intera piastrella è l'interruttore, piuttosto che righe con a 34px toggle all'estrema destra, e la sezione email ha adottato il pannello righe in modo da legge come parte della pagina piuttosto che una carta che vagava in. Non-disturbo ora appare una volta. Era nella striscia di sintesi e di nuovo nella carta silenziosa — lo stesso interruttore due volte su uno schermo. Vive con il riassunto, che è la cosa che cambia; la carta tranquilla è lasciata al tipo programmato.
· kamo-internal
Le notifiche diventano una scheda sul profilo del membro
Erano una pagina propria a /settings/notificazioni, che hanno messo una preferenza circa una persona da qualche parte diversa dalla pagina su quella persona — e ha lasciato il miss-message interruttori e-mail bloccati sulla scheda profilo, quindi le due metà "come mi dicono le cose" viveva su schermi diversi. Entrambi sono qui ora, su Proprio dopo il mio profilo. Gli switch e-mail mantengono esattamente il loro comportamento — stessa chiave di preferenza, stessa me-scoped route, stesso debounce e manalive colore — e sono redrawn con la sezione del pannello di notifica e i primitivi di riga in modo da leggere come parte di esso piuttosto che come una carta che vagava. Rimangono una sezione distinta e dicono così: che il canale ti raggiunge quando l'applicazione è chiusa, è scritto da un servizio diverso, e mantiene le proprie ore tranquille. Piegare i due set di ore in un unico controllo cambierebbe silenziosamente ciò che uno di loro governa. La scheda è solo auto, perché ogni preferenza dietro di essa è letta e scritta attraverso percorsi me-scoped — sul profilo di qualcun altro questi controlli modificherebbero il visualizzatore è proprio mentre appare per modificare il loro. Esegue l'indirizzo del conto piuttosto che prenderlo dalla scheda del profilo: le schede delle impostazioni montano uno a tempo, quindi un valore sollevato da un fratello sarebbe mancante esattamente quando un membro ha aperto prima questa scheda, che dal link Impostazioni di una notifica è ogni volta. Il vecchio percorso è andato, insieme con le sue impostazioni-menu argomento, e le tre superfici che ora ha collegato a esso costruire il percorso attraverso un aiuto — un percorso assemblato a mano in tre posti è un percorso che alla fine non è d'accordo con se stesso.
· kamo-internal
Firma un pacchetto a /settings/sign, su una rotta propria
La firma era in linea sulla scheda del profilo del membro, quindi il documento si sedeva sotto la scheda delle impostazioni striscia E la striscia sub-tab di conformità E quella scheda è di aiuto pannello — tre bande di cromo non correlate sullo strumento il membro è e due intestazioni. E' ora che... La pagina eredita NavTop e NavPri come ogni altro schermo e l'eroe band del mago è l'unico Intestazione. Finitura o partenza torna a La lista e' piena di emozioni. da, sul secchio il pacco era dentro. La lista naviga con `push`, non `sostituire`: si tratta di una mossa a un diverso schermo, quindi il browser Back deve tornare alla lista. L'allerta ha dovuto trasferirsi. È prefisso e nega per impostazione predefinita, ed è quello che un membro il cui l'occupazione è finita è limitata a. Senza `/settings/sign` su di esso, un ex dipendente potrebbe aprire la loro lista dei pacchetti e essere rimbalzato direttamente indietro a esso da RestrictedMembershipGuard il momento in cui hanno cliccato in uno — un reindirizzare il loop sulla superficie esatta che il livello esiste per mantenere raggiungibile, poiché i riconoscimenti di conformità sono una delle due cose che protegge. `restrictedRoutes.test.ts` ora spills che, compreso che il match non è un bare startWith: `/settings/signage` non è `/settings/sign`. Nessun cancello sulla pagina stessa, deliberatamente. Un membro che esecu i propri l'accordo di lavoro non ha diritto alle risorse umane e mai lo farà — il pacchetto era emesso a loro — e ogni punto dietro di esso è auto-scopio in DocsService (LegalAssignmentOwnership, non LegalPackageAccess), quindi la sessione decide il cui compito è e l'aiuto di qualcun altro risponde 404. Un nudo /settings/sign senza assegnazione ritorna alla lista piuttosto che segnalazione di un errore: non c'è nulla nella pagina senza uno e nessun modo per il membro a fornirlo lì. Un uid malformato mostra l'errore, perché rimbalzare nasconderebbe un collegamento cattivo qualcuno sta per inviare di nuovo.
· kamo-internal
Ricorda le dimensioni e la posizione di ogni nota galleggiante per dispositivo
Riaprire una nota staccata ora lo rimette esattamente dove è stato lasciato, a la dimensione era rimasta, su quello schermo. Il posto e' per DIVICE, deliberatamente. Che le note stanno fluttuando è un proprietà del membro e rimane lato server in ui-preferenze, quindi li segue ovunque; dove una nota SITS è una proprietà dello schermo in davanti a loro, e un layout che si adatta a un desktop da 27" è sbagliato su un laptop. Così i posizionamenti vivono in localeStorage, indirizzato dal membro perché uno il browser è un dispositivo, ma può essere più di una persona. Posizionamenti sopravvissuti di proposito — chiusura di una nota mantiene la sua geometria, che è l'intero punto — quindi il negozio è bloccato per membro ed evits meno-recently-placed prima, e una nota che viene cancellata (o 404s su ripristino) dà la sua slot immediatamente. Due cose che dovevano avere ragione: - logout fa un localStorage all'ingrosso.clear(), che sarebbe caduto il layout su ogni segnale. Ora cattura e ripristina questa chiave lo stesso modo in cui lo fa già per la macchia profonda. Nota ids e coordinate solo, mai contenuto. - l'idratazione aspetta l'id membro invece di correre al monte. I luoghi sono stati messi a punto, quindi ristabiliamo prima di sapere chi è questo ignorare silenziosamente il layout di questo dispositivo e tornare al Geometria cross-device. Precedenza sul ripristino è il posizionamento di questo dispositivo, altrimenti qualsiasi cosa il cross-device preferenza ultima portata — quindi un nuovo dispositivo apre ancora a un layout sano piuttosto che a una cascata — e in entrambi i modi è ri-clamato all'attuale viewport.
· kamo-internal
Il piombo su un callback è letto, non ricordato — e una chiamata può essere riaperto
Due cose, entrambi circa un evento di calendario essere un posto povero per mantenere una copia di I precedenti di qualcun altro. La finestra di dialogo eventi legge ora il cavo del CRM. Il suo nome, il numero da suonare, l'e-mail, il momento migliore per chiamare — tutto preso quando la finestra di dialogo si apre, il callback id l'evento già memorizza. Ciò che non fa è mostrare una copia congelato al momento la chiamata è stata prenotata, che è quello che la descrizione utilizzata per portare e cosa avrebbe tranquillamente detto a un membro di suonare un numero che nessuno aveva ha risposto in settimane. La descrizione è ora la nota del membro e niente altrimenti, che non può andare stallo perché non è mai stata una copia di nulla. E una chiamata in sospeso può essere aperta dalla pagina principale. Fare clic su uno riapre stesso evento del calendario nella stessa finestra di dialogo, quindi il luogo in cui un membro va a guardare La chiamata promessa è il posto in cui possono spostarla. Il risparmio passa attraverso SecurityService piuttosto che il calendario API, perché il record di callback e l'evento sono due vista di una promessa e solo il servizio che possiede entrambi può spostarli insieme — l'alternativa è una pagina principale e un calendario che non sono d'accordo quando la chiamata è, e un promemoria che spara contro qualunque il membro non sta leggendo. La riga stessa è il controllo; i pulsanti di risoluzione accanto a esso fermano il clic, quindi Nessuno segna una chiamata contattata mentre intende leggerla.

Il tuo universo ti sta aspettando.
Aggiungi i prodotti quando sei pronto: nessuna migrazione, nessun nuovo login, nessuna richiesta di pagamento.
Gratis per sempre — nessuna carta di credito richiesta