Changelog
O que há de novo.
Cada melhoria para a plataforma, como ele envia.
· kamo-internal
Vender o prazo de três anos, e parar quatro cópias do ciclo à deriva
Os ciclos de faturamento foram digitados à mão em cinco lugares. Um deles, o editor de preço de plano, oferecido "BIENNIAL" — um valor que o servidor nunca teve — assim escolhendo-o enviou uma corda BillingCycle. valor de lançamentos e economia de um preço 500'd sem nada para sugerir a opção em si era o problema. posApi agora possui BILLING CICLES, BILLING CICLE MONTHS e BILLING CICLE LABELS, e uma guarda vitest lê a fonte de enum Java e falha se os dois discordarem em constantes, ordem ou o mês conta. Duas cópias do computMRR (a página de detalhes da subscrição e a página de inscrições do gasoduto) dividiu a ordem subtotal pelo comprimento do ciclo. Este subtotal já é mensal — calcularPreço constrói-o a partir de um preço unitário por banco-por-mês — assim, um anual assinante foi relatado em um duodécimo do seu verdadeiro MRR, e um de três anos foram reportados em 36o. Ambos agora usam o subtotal diretamente, combinando a correção do lado do servidor. O plano Quote ganha o terceiro mandato. Um termo em falta cai de volta para o próximo SHORTER em vez de ser directo para o mês: citando mês a mês contra três anos Subcargas de compromisso por todo o desconto de compromisso. EncargosPorAno mesesPerCharge, porque 12/36 é 0,33 e "billed 0,33 vezes por ano" não é um frase uma interface pode usar. O Switch mensal/anual do console de faturamento torna-se um ToggleButtonGroup de três vias — um interruptor não pode expressar três estados, e o terceiro é a taxa mais barata que vendemos, por isso tem que ser visível em vez de alcançá-lo, alternando duas vezes. O seu crachá de poupança Agora dispara em qualquer termo comprometido, não o anual sozinho.
· BillingService
Contar o prazo de três anos, e citar a sua taxa para o console
BillingCycles ganha TRIENNIAL, expresso à Stripe como três ANOS em vez de trinta e seis meses para que a renovação acompanhe o aniversário de inscrição — a mesma razão ANUAL é um ano. MesesEm agora delegados para o enum, que possui o número: foi declarado aqui e mais duas vezes no serviço de assinatura, onde ambas as cópias tinham ao contrário. ************* anualizado por 12 / meses Em (ciclo)». taxas por ano. Isso é divisão inteira, corrigir apenas enquanto cada ciclo divide-se em um ano — um prazo de três anos torna-o 12/36 = 0 e anualiza cada Plano para nada. O invariante é aproximadamente a razão de quantidade para meses, por isso é expresso dessa forma agora e mantém por um ciclo de qualquer comprimento. Um novo teste também intervalo de pinos x intervaloContar aos mesesIn, de modo que a recorrência da Listra não pode derivar a partir do espaço que pretende cobrar. PlanCatalogService cita o terceiro termo para o console de cobrança de auto-serve. A termo sem uma linha de preço nem uma coluna de plano reporta nula em vez de contracção de empréstimos à taxa mensal: citando mês a mês contra três anos O compromisso seria subcotado por um terço.
· kamo-shared-library
Um período de três anos, e as regras de promoção que nunca foram lidos
Adiciona BillingCycle.TRIENNIAL para que um assinante possa se comprometer por três anos. A coluna é varchar sem restrição de verificação em qualquer lugar, então isso não precisa de migração — mas cada serviço que READS tal linha deve ser executado esta enum em primeiro lugar, porque o valorOf lança em um nome desconhecido e getCatalog carrega cada preço em Uma pergunta. Três defeitos tiveram que ser corrigidos para que o termo fosse seguro para adicionar: 1. unitPrice é uma taxa por assento-por-MONTH em cada ciclo — BillingCycles in BillingService diz isso e multiplica pela contagem de mês antes de chegar Listra. O serviço de assinatura fez o contrário: calcular o Sr. DIVIDED anualmente taxa por 12 (relatando um assento de $59 como $4,92 de MRR) e gerar faturado Um mês de taxa por um ano inteiro de serviço. Aos 36 meses, esses Erros 36x. O mês conta agora no enum, onde uma nova constante não pode ser adicionado sem responder por ele, e facturar multiplica por ele. 2. calcularPreço verificado nada sobre uma promoção, mas isActive. Aplicável planos, ciclos de facturação aplicáveis, as datas de início e fim, o mínimo de assento e o boné da redenção persistiu e nenhum foi lido — um código escrito para dois níveis descontou todos os quatro, e um código expirado nunca parou de funcionar. validatePromoCode verificou as datas e a tampa, mas não o plano ou o ciclo, então o endpoint que existe para responder "se aplica este código" discordou com a aritmética que o aplicou. Ambos agora perguntar AssinaturaPromoções. 3. FLAT PER SEAT retornou o valor de desconto nu sem multiplicar por assentos, tornando-o um sinônimo silencioso para FLAT AMOUNT: um código de "$18 por assento" levou $18 uma ordem de 25 lugares em vez de 450 dólares. Uma promoção percentual agora sai do subtotal do plano por assento após o termo desconto, não fora do plano + addons - "33% fora do custo base por assento", aplicado ao Avaliar o cliente realmente comprometido.
· kamo-internal
Mostrar o título do relatório em Enviar para IA, editável
A janela permite que um operador edite a instrução e a descrição, mas nunca mostrou o título, e o título também não estava na mensagem — a sessão em a outra extremidade tem os parágrafos sobre o que aconteceu sem a linha dizendo do que se trata o relatório. Agora está entre os dois, pré-preenchido do relatório e com um limite de 255 como o formulário de relatório, assim que as três caixas ler de cima para baixo na ordem exata o servidor junta- se a eles: o diálogo É a mensagem. Editá-lo renomeia o relatório, que a linha auxiliar diz em voz alta — a caixa diretamente abaixo dele não escreve de volta, e dois campos adjacentes se comportam diferente só é justo se a tela admitir.
· SecurityService
Coloque o título do relatório na mensagem e deixe-o ser corrigido
Uma expedição compôs as instruções do operador e a descrição do erro com o separador entre eles. O título nunca esteve nele, então a sessão abrindo o mensagem ler os parágrafos sobre o que aconteceu sem a única linha dizendo o que O relatório é sobre. Agora cabe o segundo tempo, acima da descrição: o primeiro tempo é o instrução à IA, o segundo é o relatório, e o título pertence ao Relatório. Ausente do pedido que ele retorna ao título armazenado do bug, então um despacho sem acompanhamento e uma aba de console anterior ao campo ambos ainda terra em vez de perder um alerta escrito para 400. Um operador que afie um título vago corrigiu o RELATÓRIO, por isso o o título é gravado de volta no bug — o único campo nesse diálogo que o faz. É aplicado ao lado da linha de auditoria e antes da chamada de rede: a correção foi verdade se a máquina de dev responde ou não, e um relatório não deve voltar para ser mal intitulado porque uma entrega de tempo esgotado. A descrição ainda nunca escreve de volta; reafirmar um bug para uma IA não é reescrever o que o membro arquivou.
· kamo-shared-library
Carry the report title through a Send To AI dispatch
The dispatch sent the operator's instruction and the bug description and nothing else, so the one line saying what the report is ABOUT never reached the session reading it. AiDispatchRequest gains titleText, and the audit row gains a column to record what was sent. titleText is optional: a request naming no title uses the bug's stored one, which is what keeps an unattended dispatch — and a console tab opened before the field existed — working rather than losing a written prompt to a 400. Unlike bugText, a title the operator sharpens is also written back onto the report. A vague title is wrong in the queue for everybody, not just in one hand-off; a restated description is not a rewrite of what the member filed.
· kamo-internal
Responder a uma mensagem, e levar o leitor para o que ele responde
MediaService carregou um pai de resposta e a citação para desenhar para ele desde "enviar a citação com um responder, não apenas o ponteiro"; esta é a metade que o mostra. Três janelas desenham conversas — membros bate-papo, social, e tickets de apoio — e todos os três recebem a mesma resposta, porque um membro que aprende-o em um e não o encontra no próximo lê que como o recurso sendo quebrado em vez do que ausente. Um componente ReplyQuote em duas posições: a tira acima do compositor enquanto a resposta está sendo escrito, e o bloco dentro da bolha uma vez que foi enviado. Eles são o mesmo objeto em dois momentos, e dividi-los é como os dois deriva — você escolhe uma mensagem, lê uma tradução dela, enviar, e são mostrados outro. As suas cores derivam todas da cor actual, de modo que o único componente se encontra uma bolha primária de mensagem própria, uma bolha de cinzento recebido e na superfície do compositor sem ser dito Que. A citação é resolvida através da resolução do chat-coreReplyQuote, que prefere o pai carregado sobre a antevisão armazenada do servidor. É isso que faz uma correção a uma mensagem chegar a cada citação apontando para ele, e um não enviado dizer isso, em vez de congelar o que o pai disse no momento em que foi citado. Resolvido uma vez por história mudar para um mapa em vez de por bolha por renderização: MessageBubble é memorizado em acessórios rasos, então uma citação reconstruída em linha iria re-render cada responder na conversa em cada tecla. Clicando em um pergaminho de citação para o original e pisca-lo (app/lib/chat/messageJump.ts). O flash é o ponto — um fio de bolhas de aspecto semelhante que desliza não indica ao leitor qual a mensagem que foram levados. É nível DOM em vez de um id destacado em estado, que iria re-render o lista inteira duas vezes para um efeito que toca um elemento por um segundo, e honra prefere movimento reduzido. Uma resposta cujo pai é mais velho do que a página carregada ainda desenha sua citação, da antevisão do servidor; simplesmente não é um link. O alvo pertence à janela e não ao compositor, porque duas coisas precisam dela — a tira e os pais no envio — e um deles não é o compositor. É capturado quando o envio starts, a dispensa da tira é desactivada durante a duração, e sem enviar a mensagem a ser respondida deixa cair a resposta em vez de a colocar debaixo de algo que o remetente retirou.
· kamo-internal
Offer Move on a right-clicked message, not only on a ticked one
Right-clicking a message offered Reply, Reply all, Forward, Report Spam, Archive, Label and Delete. Filing it anywhere other than Archive meant dismissing the menu, ticking the row's box, and using the selection bar — a detour nobody has cause to guess at, and the reason this came in as a request. The reading pane's own menu had the same gap. The two are one menu, so one entry fixes both, placed between Archive and Label: Archive is a move with the destination already chosen, and that is the order the selection bar already lists them in. Which messages it moves follows the rule `v` has always followed. Right- clicking a row that is part of the ticked selection is a gesture about the selection, so the bulk picker opens on all of them and its header says how many — that is the "(s)" in the request. A row nobody ticked, and the reading pane, are about the one message the menu was opened on; borrowing the selection there would move mail the member never pointed at, or, with the boxes empty, nothing at all. Archive now goes through that same single-message move rather than its own fetch(). It had been leaving the reading pane showing a message it had just filed elsewhere, because a move has to take the row out of the list, close the pane over it, and mark both folders' counts stale — which is not obvious, and is worth deriving once. Verified: 375 tests pass across app/components/messages and app/lib/email, 4302 across the repo; tsc clean on the .next-excluded config; eslint on the touched files byte-identical to baseline (0 errors, 8 pre-existing warnings). The menu was rendered and screenshotted to confirm the entry sits where the report asked. No new translation keys — browser.moveToFolder already ships in all 21 locales. Note: check-i18n-keys is red on main, 15 keys, all from the smart-folders commit at 0357c52b awaiting its dictionary re-sync. Identical before and after this change.
· kamo-internal
Ir para Configurações lidera o menu Configurações, acima da lista de aplicativos
Relatado com duas capturas de tela — uma org com onze aplicativos habilitados, uma com dois — cada um circulando "Ir para Configurações" na parte inferior da subopção Configurações menu com uma linha desenhada até ao topo da lista. Na página onze org a entrada sentou- se após uma barra de rolagem, por isso a coisa mais simples que o menu oferece foi A única coisa que tinhas de rolar para alcançar. Cada outro menu de subopção no produto já leva com a entrada que abre a ferramenta em si — Mail with Compose / View My Emails, Docs with Manage Os meus Docs, ambos na primeira secção sem título. As configurações eram mais estranhas, então... agora corresponde em vez de obter uma colocação própria: Ir para Configurações e Personalizar navegação compartilhar a seção principal, em seguida, os tópicos principais, em seguida, Aplicativos habilitados. Sem alterações de cordas, e todas as três posições (bar popover, O voo de carril colapsado, a lista em linha de carril expandida) lido desta lista.
· KlusterServices
Troca 64G na máquina de dev, e acesso de captura que falha intermitentemente
A página branca em branco era a VM fora de memória. 16Gi de convidado RAM com NO swap significa "fora de memória" vai direto para o assassino OOM: aplicações morrem no meio do trabalho, e desta vez o convidado inteiro ficou sem resposta — incluindo o agente de abastecimento. Isso chega mais longe do que a VM, porque SecurityService pré-voo desse agente antes de cunhar um link KamoDesktop, então uma VM encravada virou cada lançamento em uma aba de navegador em branco: a aba abre em branco e só navega uma vez que um link volta, e nenhum link nunca veio. Mais RAM não está disponível — o k1m1 já está ~86% alocado, então a memória está não existe para dar. Um arquivo de swap 64G é, e é a diferença entre "low por um momento" e "seu editor se foi". vm.swappiness=10, deliberadamente: esta troca assenta num volume de Longhorn replicado através da rede, por isso é muito mais lento do que o disco local e deve ser uma rede de segurança em vez de uma rotina. O padrão 60 seria página para fora da área de trabalho ociosa aplicações para criar espaço para cache, através da rede, que é exatamente o Troca errada aqui. O script se recusa a construir o arquivo se o espaço não estiver lá — preenchendo o root sistema de arquivos para fazer swap iria trocar um problema de memória para um disco cheio, que é cada vez mais difícil de desfazer. Verificado idempotente, executando-o contra o VM já configurado: relatou "já ativo em 64G" e nada mudou. Também adiciona KamoDesktopAccessFlapping. KamoDesktopAccessBroken precisa de dez UNBROKEN os minutos de falha, e esta falha nunca falhou tão longa em um trecho — o agente respondeu algumas sondas e não outras — de modo que o acesso foi gravemente quebrado para o pessoa tentando usá-lo enquanto o alerta ficou quieto. Intermitente não é bom; metade de uma área de trabalho está avariada.
· kamo-internal
Faça pastas inteligentes alcançáveis, e faça clicar em um trabalho
Uma pasta inteligente é uma pesquisa salva que fica onde uma pasta está: nomeie uma pergunta uma vez ("não lido, com um anexo") e está a um clique de distância, atual cada vez, em todas as pastas. Nada é movido e nada é mudado. Tudo foi construído e nada estava ligado. Três intervalos, cada um por si. SmartFolderModal não tinha importador, então lá não era maneira de criar um — e a seção da barra lateral estava fechada em já ter um, que deixou o único ponto de entrada atrás da coisa que criou. Clicando em um chamado noFolderSelect(' smart ' + id) no setSelectedFolder sem branch em qualquer lugar a jusante, então loadMessages pediu IMAP para uma pasta literalmente chamada smart <uuid> e errou. label teve o erro idêntico. E o endpoint retornou índice bruto linhas que a lista não pode renderizar, fixadas no EmailService ao lado disto. virtualFolders.ts agora possui a codificação de ambos os lados compartilhar. loadMessages branches on o; search substitui a caixa de entrada, porque o servidor de e- mail não pode ser solicitado para pesquisar uma consulta salva; e as três atualizações em tempo real recarregam as alterações de qualquer pasta enquanto uma consulta salva está aberta, uma vez que correspondência por nome significa que nunca foi atualizada. A contraparte de uma linha é julgada pela própria pasta da linha também — pela vista, cada mensagem de saída em uma pasta inteligente foi rotulada com o próprio endereço do leitor. Além disso: o ícone com o qual uma pasta é salva é agora o ícone que mostra (o selecionador oferecido seus nomes como texto literal — botões lendo "inbox", "zap" — e a barra lateral desenhou um funil independentemente); condições de texto obter o operador o servidor tem sempre suportada; e SmartFolderCondition pára de emprestar a união de campo de regras de filtro, que é um conjunto diferente de nomes e forçou todas as condições através de `como qualquer `. Verificado: teste npm — 4202 passe, e as 13 falhas são o mesmo chat/@kamo A origem/principal falha sem esta alteração. tsc e eslint byte-identical to basal. Chaves de dicionário empurradas primeiro, em d91e6b66.
· MediaService
Enviar a citação com uma resposta, não apenas o ponteiro
media objs msg.parent id esteve lá o tempo todo e este endpoint aceito paiId no caminho desde threading foi modelado, mas uma resposta cruzou o fio carregando apenas a identificação do que respondeu. Isso é o suficiente para thread e não o suficiente para desenhá-lo: a citação acima de uma resposta nomeia um autor e mostra uma linha do que escreveram. Um cliente poderia resolver isso a partir das mensagens que já contém, e para a maioria responde que sim — as pessoas respondem o que ainda podem ver. É o resto que Decide isto. Uma resposta para algo mais antigo do que a página carregada, uma conversa reabriu em sua página mais nova, uma linha de suporte que corre para centenas de mensagens: em todos aqueles o pai não está no cliente em tudo, e uma citação que torna vazio nos casos em que o recurso existe não vale a pena ter. Então paiSenderName, paiMessagem e paiIsRemovido viajar com o responder. Está quase livre. Ambas as consultas de histórico já foram deixadas de lado. então o corpo do pai está no contexto de persistência antes do ChatReplyPreviewService é chamado. Só o AUTOR precisa de busca, e apenas para os pais que não são em si mesmos na página — uma página cujas respostas ponto dentro de si emite no Perguntar. Essa é a forma que já está. estabelecido. Três coisas que a versão ingénua se engana: A resposta de envio omitiu inteiramente o pai. Aceito no caminho, ecoou em nenhum lugar na saída — então a única janela garantida para estar mostrando a mensagem no momento em que é enviado foi a única janela com nada para citar. Agora. carrega a mesma visualização que o live frame faz, e ambos são construídos a partir de um resolução para que os dois lados da conversa não podem discordar. Em um bate-papo público as mensagens do visitante são armazenadas no SISTEMA da org membro, que é geralmente o proprietário respondendo-lhes. Nomeando o autor de um pai a partir desse membro colocaria o nome do agente nas próprias palavras do visitante — o mesma armadilha contra a qual o nome do remetente já está guardado, por isso a antevisão toma a O mesmo visitante sobrepôs-se, na história e na imagem ao vivo. Cortes de truncagem num ponto de código. substring conta UTF-16 unidades, e cada emoji é um par substituto; um pouso de fronteira dentro de um produz um substituto solitário, que não é um personagem e não sobrevive intacto ao JSON. findAllByIdIn deliberadamente não filtra é removido: uma resposta a uma mensagem que foi posteriormente não enviado ainda desenha uma citação, e todo o trabalho da citação há a dizer O original desapareceu.
· kamo-internal
Deixe uma conversa em grupo, e mantenha a linha que grava
Saindo é oferecido no pé da lista popover, separado das fileiras acima dele porque pertence ao membro em vez de a qualquer pessoa na lista, e Confirmado com uma frase e não com as duas palavras em linha que confirmam as utilizações da remoção: é a única ação aqui que alguém não pode desfazer para si mesmo, ea formulação tem que dizer que você não pode voltar sem ser adicionado. Sua própria ação, não "remover" dirigida a si mesmo - o servidor recusa isso. A dois atos mantêm sua própria confirmação e sua própria linha na transcrição, e um membro nunca é perguntado se eles querem se remover. Nada para renderizar sobre o sucesso: a conversa anuncia sua própria partida através do mesmo evento como sendo removido por outra pessoa, que já fecha a janela e deixa cair o linha da lista. A transcrição precisava de duas soluções para valer qualquer coisa. O nome do sujeito era resolvido fora da lista de membros da sessão — que está vazia dessa pessoa em exatamente o caso para o qual existe uma linha "removida" ou "esquerda" "Sage removido" com o nome em branco; o servidor agora envia-o e a lista é apenas Um recuo para as fileiras mais velhas. E uma partida é armazenada como a mesma linha de trânsito que um remoção, ditos à parte pelo ator sendo o sujeito, por isso sem esse ramo o Os dados dizem "Daniel removeu Daniel". Precisa de @kamo/chat-core 0.1.13 e MediaService 549b29f, que também é onde estes as linhas pararam de desaparecer da história ao recarregar.
· MediaService
Deixe um bate-papo em grupo e mantenha as linhas de entrada/saída na transcrição
Sair é DELETE /sessions/{guid}/membership, não removerMembro destinado você mesmo — essa rota recusa auto-remoção de propósito. Remoção é feita TO alguém e nomeia o actor; sair é algo que se faz. Ambos escrevem o mesmo linha de trânsito (isJoin=false) e os clientes dizer-lhes se o ator e o sujeito são a mesma pessoa, que a linha já registra, então este Não precisa de nova coluna. Grupos apenas, pelo mesmo motivo remoção é: deixando um lado de um bate-papo de duas pessoas iria terminar para esse lado, enquanto o outro ainda poderia Escreva-o. A solução maior está debaixo dela. Um MediaObj transportando um trânsito não tem MSG, então histórico serializou-o como mensagemType MESSAGE com um corpo nulo — uma bolha vazia. Junte-se e deixe linhas, portanto, existiu apenas ao vivo, no quadro NATS, e desapareceu na próxima recarga: "Sage added Daniel" era visível para quem quer que fosse a observar e a ninguém depois. O que fez as linhas de remoção e de partida — o aqueles que existem para explicar por que alguém não está mais na sala — o menos coisa durável na conversa. A história serve-os agora como TRANSIT com o ID do sujeito, nome e direção. Ambos os caminhos nomeiam o sujeito. Em uma remoção ou uma partida que a pessoa se foi a sessão pelo tempo em que o frame pousa, então a lista de membros de cada destinatário tem já os deixou cair e não havia nenhum lugar para resolver o id: a linha renderizada com o nome em branco. Requer biblioteca- partilhada- kamo com TransitName.
· kamo-internal
Abra em Chats, uma seção Favoritos e uma conversa em grupo que você pode gerenciar
Cinco pedidos de um membro, todos eles sobre a mesma lacuna: o painel de Chats podia mostrar-te conversas mas não te deu nada a ver com elas. Chats agora é a primeira aba e a primeira que o painel abre. O botão que abre este painel é o botão Chat – a aterrissagem no diretório tornou o comum caso, continuando uma conversa que você já está tendo, uma aba interruptor de distância todas as vezes. Uma seção dobrável Favorites fica acima da lista, por sub-tab, e é obtido separadamente de propósito. As páginas da lista por última atividade, então a conversa estrelou precisamente porque importa, mas tranquilo por um mês é várias páginas abaixo; uma seção reunida do que já havia sido rolado em vista mostraria essa pessoa alguns dias e não outros. A dobra é lembrado por exemplo, pela mesma razão de preferênciaKey is a prop — the Centro de interação e uma coluna de troca de janela maximizada são duas montagens ao vivo e de outra forma bater-se-iam uns aos outros. A estrela é oferecida apenas onde o servidor pode gravar um, que é CHAT: uma conversa social não tem associação remo para o espectador em tudo. Os chats em grupo podem ser nomeados. Um nome escolhido pelos membros é verificado ANTES do derivado em toda parte é lido — a linha, o título da janela, a lista header — porque caso contrário "Alguns bate-papo" reverte para "Sage, Claudius +1" o momento em que alguém é adicionado, que é o nome do caso existe para. Um nome em branco é não um nome; ele cai para trás em vez de apagar o título. Um botão de lista numa janela de grupo abre o popover que responde "quem mais é aqui" — até agora a barra de título "+1 mais" foi a resposta completa e lá não tinha como o expandir. Nomeação e remoção vivem lá também, porque ambos são coisas que você decide enquanto olha para a lista de pessoas. O título da janela já elipsou; o que estava faltando era a maneira de ver além dele. Alguém pode ser removido. A linha de transcrição para ele existe desde o starting — SystemMessage sempre renderizou "{actor} removido {target}" para uma trânsito com isJoin=false — e nada tinha escrito um. Remoção é confirmado dentro da linha em vez de numa janela: um modal sobre um popover é dois camadas de cromo para uma pergunta de duas palavras, perguntado sobre uma lista que iria encobrir. Ser removido você mesmo fecha a janela, porque cada pedido que ele poderia fazer a partir desse ponto responde 403 e um bate-papo que parou silenciosamente de trabalhar é pior deixado aberto. Cada regra sobre quem pode fazer o que é do servidor, e sua redação é mostrada verbatim quando se recusa. Uma regra declarada genericamente lê como um erro. Needs @kamo/chat-core 0.1.12 e MediaService aed30b1.
· MediaService
Nomeia uma conversa em grupo, estrela uma conversa, remove alguém de uma sala
Três rotas, e cada recusa é uma regra que alguém tem que ser capaz de distinguir de uma parada — então cada um carrega seu próprio código em vez de um 4xx compartilhado. DELETE Apenas membros, apenas grupos, nunca Tu mesmo. A regra do meio é a que vale a pena afirmar: uma conversa de duas pessoas com uma pessoa removida não é um bate-papo menor, é uma conversa um lado pode não ver mais e o outro ainda pode escrever. O último não é um restrição, mas uma admissão — deixando um chat precisa de sua própria confirmação e sua própria redação na transcrição, portanto, isso recusa em vez de meio-implementar Ele. Qualquer um na sala pode remover qualquer outra pessoa, que é a simetria do add rota: cada participante já poderia ter trazido essa pessoa, e um conversa cujo único gestor é o seu criador torna-se incontrolável o momento Essa pessoa sai da empresa. Remoção publica MEMBRO REMOVED (por isso o removido o próprio cliente do membro pode fechar a janela) e o trânsito de licença (por isso não ler, para todos ainda lá, como alguém que simplesmente ficou quieto). PATCH /sessions/{guid}/title. Apenas chats em grupo: uma conversa direta é nomeada depois da pessoa com quem você está falando, e um nome colocado sobre o topo esconde o Na verdade, a linha existe para te dizer. Partilhado, não privado — todos o vêem, e SESSION RENAMED carrega o novo nome então janelas abertas mudam sem pedir. A a resposta é incorporada em um LinkedHashMap em vez de Map.of porque um nome limpo é nulo, e Map.of teria jogado na saída de uma chamada que já tinha Bem sucedido. PUT /sessions/{guid}/favorite. A opinião do espectador, então o único portão é ter uma linha de membros — e o 404 quando não há nenhum caso real, não padding: uma conversa social não tem nenhum membro linhas em tudo. Recusa de batidas relatando um sucesso que o membro encontra na sua próxima recarga. A lista de bate-papo carrega é favorito e título, e `favorites=true` responde a cada Uma seção de favoritos que teve que ser bipada em vista colocaria uma estrela A conversa foi logo atrás do pergaminho estrelou para escapar. Requer kamo-shared-library em 2352064a e as duas colunas que documenta.
· kamo-shared-library
Um chat pode ser nomeado, estrelado, e ter alguém removido dele
Três campos e um método, todos os quais o recurso de chat já tinha o forma para e nenhum dos quais poderia gravar. MEDIA SESSÃO CAT ganha TÍTULO. Essa tabela existe precisamente para segurar o que é verdade de um bate-papo e de nada mais, e até agora realizada apenas o seu próprio estrangeiro Chave. Null não é "sem título": é a instrução para continuar derivando o nome de quem está na sala, que é o que cada bate-papo fez antes e que bate-papo de duas pessoas deve continuar fazendo para sempre — assim limpar um nome restaura o derivado um e nenhum backfill é necessário para as conversas que antecedem isso. MEDIA SESSION MEMBERS ganha IS FAVORITE, ao lado de IS MUTE. Ambos são de um membro opinião privada de uma sessão, invisível para todos os outros nela. Apenas um CHAT sessão pode levar um, e não porque o recurso foi escopo para baixo: um sessão não tem linhas nessa tabela em tudo (que pertence à organização), e uma sessão de SUPPORT pode ser visível para um agente através da atribuição do ticket sem uma. Uma estrela oferecida nessas fileiras seria um controle sem lugar para Escreva. RemoveMemberFromSession é o espelho do addMembersToSession, com a mesma forma caminho de propósito: a linha de membros vai e um MediaObjTransit com isJoin=false fica na transcrição para sempre. IS JOIN tem estado nessa mesa desde que iniciando e os clientes sempre renderizaram "{ator} removido {alvo}" para nada jamais havia escrito um. As consultas na lista de conversas carregam os campos novos e as respostas de uma consulta favorita todas as conversas estreladas sem serem chamadas. Esta é a razão pela qual é um separado pesquisa em vez de um filtro no paged um: as páginas da lista por última atividade, então uma conversa estrelou precisamente porque importa, mas quieto por um mês é várias páginas abaixo, e uma seção reunida de tudo o que já tinha sido Percorredo em vista mostraria essa pessoa alguns dias e não outros. Ambas as colunas precisam de DDL à mão — InicializerService não é implementado: TABELA ALTER media session members ADD COLUNN is favorite boolean NOT NULL DEfault false; TABELA ALTER media session chat título ADD COLUNN varchar(120);
· kamo-internal
Adicionar membros em massa e membros da equipe, com uma importação CSV/XLS
Duas novas telas ao lado dos botões Adicionar em /account?tab=members and ?tab=equipe-membros, em /settings/member/bulk-new e /settings/equipe-member/bulk-new. Ambos os botões retiram a sua redacção das etiquetas de membros/membros da equipa da organização. A grade é uma planilha: uma linha por pessoa, campos como colunas, um novo branco linha anexada no momento em que o último é digitado, e um controle de remoção em cada Remem, mas aquele que se segue. Por cima dele estão as configurações que todo o lote é criado com -- nível de segurança (fixo pela tela), fuso horário (o org's padrão), departamento, título do trabalho, status da associação e a caixa de correio, que começa para os membros da equipa e desliga para os membros. Nada é criado até "Terminado -- Criar ..." é pressionado; o botão então gira, e a grade vai Inerte até que cada registro tenha sido respondido. O sucesso parcial é o resultado desenhado. O endpoint cria cada registro em seu transacção própria e relatórios sobre cada um, por isso linhas que caíram colapso e linhas que não ficaram exatamente onde estavam com a razão ligada ao célula que causou isso -- corrija-os e termine novamente, e apenas o restante é Tentei. E-mails duplicados e nomes de usuário são capturados no navegador também, então o admin vê-los enquanto ainda editando em vez de depois de dois terços de um arquivo tem foi criado. Um nome de usuário deixado em branco mostra o nome do servidor mais provável menta; um digitado à mão é verificado quanto à disponibilidade como é digitado. "Importar ... Lista" preenche a grade de um CSV/XLS/XLSX e não cria ninguém. Coluna mapeamento reutiliza FieldMappingInterface -- o mesmo componente de arrastar para conectar o O assistente Importar os Leads usa, que já é genérico na sua lista de campos do sistema -- após um passe de auto-mapa que corresponde aos nomes de cabeçalho que uma exportação de RH realmente usa. As coerções dos membros vivem no membroBulkFields.ts com testes: local de trabalho de "Work From Home" / "In Office" / "Hybrid - 3 dias no escritório", prefixos e sufixos contra as próprias listas configuradas da org, "Último, Primeiro" e "Dr. Jane Q. Público" dividido em partes, e-mails minúsculas. Duas correções caem do edifício sobre esse componente: FieldMappingInterface ganha uma opção noModifiersChange. Sua divisão e valor os modificado virtualFieldId nomeou uma coluna virtual mas não tinha como saber qual divisão produzi-lo -- cada "Nome Dividido" que o usuário configurou foi deixado em silêncio quando as fileiras foram lidas. O adereço é aditivo; o assistente de leads é intocado. A importação sempre analisa com hasHeaders: true e re- attachs linha 1 em si quando O administrador diz que o arquivo não tem nenhum. parseO próprio ramo falso do File executa uma heurística que descasca linha 1 fora de qualquer maneira quando parece header-ish, e então relata hasHeaders: true porque sintetizou as etiquetas "Field N" -- então ecoando esse valor de volta para o botão gira-o para trás do administrador e o próximo re-parse come um Remar. Em um membro importar essa linha é uma pessoa que nunca recebe uma conta, com nada no ecrã para reparar.
· SecurityService
Bulk-create members and team members, one transaction per record
The two bulk-add screens need to create a spreadsheet of people in one submission and report on each of them individually. That could not be built on the existing endpoints, so the creation pipeline moves out of the controller. MemberCreationService now owns it, marked REQUIRES_NEW. Each record therefore commits or rolls back on its own, which is the whole premise of the screen: a failure at record 7 leaves 1-6 created, and only the rows that failed stay on the page to be corrected. Had the loop stayed in the controller, Spring's proxy would not have seen the call (self-invocation), every record would have shared one transaction, and the first caught failure would have marked it rollback-only and discarded the members that had already succeeded at commit time. MemberBulkCreator runs the batch and is deliberately NOT transactional for the same reason -- an outer transaction would be poisoned by exactly the exceptions it exists to absorb. It also refuses an email or username repeated within one submission, which would otherwise surface as "already a member" and send the admin looking for a member this very request had created a second earlier, and it releases those claims again when a row fails for some other reason. Both single-record endpoints now delegate to the same service, so the bulk path runs an identical sequence: reuse the User matching the personal email (case-insensitive, oldest wins), refuse a duplicate membership with 409, create through MemberService, mirror into the securityProvider org, re-materialize rights, then send the verification and WELCOME emails. Two other changes fall out of this: MemberUsernameGenerator fills in a blank username, in the order the product asked for -- first.last, f.last, first.l, first.m.last, then the same four numbered. It folds accents, respects the 20-character column, never ends on a separator and never mints a reserved handle. Availability is probed globally, because login resolves LOWER(u.username) across the whole table before it looks at any org. UsernameAvailabilityRepository is SecurityService-local so this ships without a shared-library version bump; the shared finder is exact-case and throws outright once two rows do collide. A hand-typed username that collides only by case is now refused rather than creating a second user. That pair is unrecoverable: both accounts become ambiguous at the sign-in form and findByUsername throws for each of them thereafter. The single-record form always sends a username it has already validated, so nothing regresses there. **************** learns extractOrgId, which is extractUserId's sibling and was missing only because the two controllers using it predate the pattern. Four baseline entries go with it.
· kamo-internal
Diga que o aplicativo está em beta, e que os dispositivos Apple não estão prontos
Dois fatos que um membro precisava antes de digitalizar qualquer coisa: KamoMobile é beta, e ainda não existe nenhuma construção para um iPhone ou iPad. Ambos mudam se alguém deve começar em tudo — um membro da Apple não tem nada a fazer nesta página — então eles vão em um banda ABOVE o deck de instalação em vez de como uma nota de rodapé sob um código que tem Já foi escaneado. Deliberadamente não é um DeckPanel: o deck é as instruções, e um aconselhamento em o mesmo formato de cartão que "Passo 01" é desnatado como passo 00. Um grande quente-afinado cartão, split — advertências beta à esquerda (se aplicam a alguém que pode instalar), Apple à direita (alguém que não possa). O herói ganha uma pílula Beta ao lado do kicker para que o status seja lido no mesmo momento como o nome da aplicação. A sua cor precisa de `&&&`: as forças de envoltório do herói `& .operator- label { color: #fff!important }` em (0,2,0), que bate uma planície cor sx mesmo com `!importante` próprio. Índices de entrada de convés deslocar 0-3 → 1-4 para que a cascata ainda lê de cima para baixo. Verificado: tsc limpo, `npm teste` verde (12 guardas + 4078 testes), e um real 'próxima compilação' completa.
· kamo-internal
Uma guia de Receitas e detalhes de pagamento na cotação impressa
Configurações → Conta → Recebíveis é onde uma organização define a conta bancária os seus clientes pagam: o beneficiário, o banco, o encaminhamento e os números de conta, e quer imprimam nas citações. Uma citação levantada do /leades/view/:id?tab=quotes então transporta um bloco de remessa, de modo que uma empresa que recebe um sabe para onde enviar o dinheiro sem telefonar para pedir. A tela é construída em torno de uma ideia. Alguém aqui está a decidir publicar um banco. conta para cada empresa que recebe uma cotação, então eles devem estar olhando para o bloco que será publicado em vez de em uma descrição dele. A antevisão ao lado do formulário está o componente SAME que a folha de citação renderiza — QuoteRemitânciaBlock, partilhados por ambos — de modo que os dois não podem derivar. Três estados por campo numérico, e a diferença é toda a interação. Vazio: Escreva. Armazenado: o servidor responde com os últimos quatro dígitos e nada mais, então o campo mostra "•••• 4417" e oferece-se para substituir. Substituição: viva e vazia, com Um caminho de volta que não envia nada. Um campo de substituição vazio NÃO é enviado, o que significa esta tela não pode limpar um número armazenado — "Eu cliquei em Change, pensei melhor dele, em seguida, salvou outra coisa" não deve apagar silenciosamente a conta de depósito. VER RECEBÍVEIS e GESTÃO RECEBÍVEIS são agrupados por NOME em Contabilidade e Finanças em permissãoGroups.ts. Eles não possuem ServiceType, e um direito com ServiceType a organização não está habilitada renderiza em nenhum editor em tudo — que é como dezenove direitos outrora se tornaram ingratíveis no entanto a org foi licenciada. A aba, o link do menu de configurações e o cartão de desembarque cada portão no par; a aba também admite o proprietário da organização, combinando com a infra- estrutura, porque um novo direito é Concedida a ninguém no dia em que for lançada, incluindo quem a conceder. O bloco de remessa é resolvido SERVER-side, mesmo para a prévia da equipe, e é ausente, a menos que a impressão esteja ligada e ambos os números sejam armazenados. Uma conta visualizada número que diferiu do enviado é o único erro que um cliente encontra antes Qualquer um sabe.
· kamo-signer-monorepo
Imprima os detalhes da ACH do vendedor na cotação do cliente
A página que um cliente abre a partir de um link de compartilhamento agora carrega um bloco de remessa: o pagamento é feito para, o banco receptor, e o roteamento e conta números, seguido pelo aviso de que a conta leva apenas créditos. Senta-se depois dos termos e antes do agente. Nesse ponto o leitor concordou os números, e a próxima pergunta é para onde enviá-los. Aqui não há nada. O servidor emite o bloco inteiro ou nenhum — é ausente, a menos que a organização publique sua conta E tenha ambos os números armazenados — assim uma cotação nunca carrega metade de uma instrução de pagamento, que é o tipo de O funcionário responsável pelas contas actua antes de a transferência voltar com uma taxa anexa. A marcação e CSS espelham o QuoteRemitância do kamo-internalBlock classe-para-classe, como O resto deste documento já o faz. O aviso de fraude-controlo mantém o seu próprio em vez de ser dobrado nos termos prosa: é um limite do que o conta pode ser usado para, e a pessoa que paga tem que encontrá-lo sem ler o Página inteira. A sentença de forma jurídica tem uma linha de linguagem simples sob o qual "ACH Credit Only" não é o que faz alguém agir corretamente — "não agendar um A retirada contra ela" é. Figuras agrupadas recebem dígitos tabulares e um pouco de rastreamento. São transcritos por entregar em uma forma bancária, e isso é o que impede um 3 ser lido como um 8.
· SecurityService
Armazene a conta de depósito da org e imprima-a nas cotações
`/api/segurança/recebíveis` é onde Configurações → Conta → Receitas lê e escreve. Dois direitos, não um: VER RECEIVÁVEIS lê a aba, GESTÃO RECEIVÁVEIS Muda-o. Nem um direito de pagamento — VER ORG FINANCES governa a saída de dinheiro a organização, estes governam a conta que pede aos clientes para pagar, e Alguém que reveja lotes de comissão não tem nada a ver com isso. A o proprietário da organização é um detentor implícito, porque um direito novo é concedido a ninguém no dia em que ele implantar, incluindo quem iria ligar o recurso, e Sem aquela filial, esta nave está adormecida. Os números entram, mas nunca voltam. Uma leitura de respostas com os últimos quatro dígitos, que é o que um administrador que confirma a conta certa está no arquivo na verdade necessidades; mudar uma significa digitá-la novamente na íntegra. As escritas são parciais — apenas o chaves que o corpo carrega são aplicadas — para que a tela pode salvar o interruptor de impressão sem reenviar um número de conta que nunca tenha mostrado. OrgRecebiblesService verifica o dígito de verificação ABA em vez de apenas o dígito Contar. Um par transposto é o erro de digitação mais comum que existe e passa um verificação de comprimento ao nomear um banco diferente ou nenhum, e o único momento barato para Apanha-o enquanto alguém olha para o campo. Também se recusa a deixar o organização imprimindo uma instrução que não pode completar, verificado sobre o RESULTADO de atualização: ligar a impressão sem conta e limpar a conta enquanto a impressão está no mesmo erro visto de dois lados. Mudando onde a terra de pagamentos de uma organização é recusada enquanto A fazer-se passar por Comissário Controlador. As superfícies de citação resolvem o bloco através do QuotePresentation, assim a pauta pré-visualização e página do cliente não pode discordar sobre o documento — incluindo sobre a sua ausência. Uma falha de descodificação não produz nenhum bloco em vez de um erro: a cotação que renderiza sem detalhes de pagamento é recuperável pela chamada do cliente o agente nomeado nele; um que não vai render em tudo não é.
· kamo-shared-library
A conta de depósito uma organização pede aos clientes para pagar em
Seis colunas em 'orgs' e o contrato de fio para imprimi-los em uma citação, assim um empresa que recebe um sabe onde enviar o dinheiro sem telefonar para Pergunta. Os números são AES-GCM criptografados em repouso através do SecretCipher, o mesmo envelope As credenciais GoDaddy usam, e carregam `@JsonIgnore` por uma razão mais afiada do que Que: serializa esta entidade inteira e necessidades Sem sessão. Uma organização que não tenha ligado a impressão publicou Nada, e uma serialização de toda a entidade não deve fazê-lo por eles. Os últimos quatro os dígitos sentam- se ao lado do texto cifrado na janela clara para que um ecrã de configuração possa dizer "••• 4417" sem ter uma chave. «ACH ON QUOTES» é um booleano nulo, não «NULL DEFAULT FALSE». NÃO NULL adicionar contra uma tabela povoada é a forma de uma coluna que o ddl-auto do Hibernate não consegue aplicar, e uma coluna que silenciosamente falhou em ser criada derruba todos os serviços que mapeia. Null resolve false em todos os lugares, que é o padrão seguro para uma campo que publica uma conta bancária. A regra é que o bloco imprime ou não. em tudo: imprimir em, mais ambos os números. Uma instrução de pagamento que falta metade da sua informações de roteamento é um um funcionário de contas-paga ainda atua em, e A transferência volta dias depois com uma taxa anexada — o silêncio envia-os para o Agente nomeado mais acima da página em vez disso. Leva os números descodificados como argumentos em vez de lê-los, porque o texto cifrado precisa de um feijão primavera e esta classe é deliberadamente a única resolução estática a prévia da pauta e compartilhamento de página do cliente. VER RECEIVÁVEIS (293) e GESTÃO RECEIVÁVEIS (294) são uma nova raiz e sua única criança, NÃO um ramo do pagamento subtree. Os dois são direções opostas de dinheiro e riscos opostos: VER ORG FINANÇAS lê o que está partindo, e o dano de uma conta de depósito substituída é cada cliente facturado a pagar a um estranho. Um controlador que avalia lotes comissão não tem negócio editá-lo. Ids Re-derivado de origem/main; 288-292 permanecer queimado.
· kamo-internal
Baixe o aplicativo de um URL que diz KamoMobile
o telefone mostrado enquanto baixava era o antigo. O mesmo bytes agora também sentar E o QR aponta para lá. Uma cópia, não uma movimento: o objeto antigo ainda responde, então códigos QR já impressos ou fotografados Continua a trabalhar. que URL é compilado no binário instalado, então ele só pode mudar em um Reconstrução nativa, e a base instalada é 0.1.9 e não pode se auto-atualizar. Também registra, onde a próxima pessoa irá olhar, por que a configuração profunda link Ainda diz kamocopilot:// e o que tem que acontecer antes que possa parar.
· kamo-internal
A linha da marca do trilho lê "De Volta ao Ecrã"
Nomeia o destino no vocabulário próprio da plataforma em vez do genérico "Vai para casa". O rótulo de aria da linha expandida segue o texto visível, então o o nome acessível e a etiqueta que um utilizador de voz fala são a mesma string.
· kamo-internal
Diga "Ir para casa" sob o nome org no trilho expandido
A linha de marca do trilho expandido sempre navegou para casa, mas o nome org sozinho lê como um rótulo, por isso o destino era invisível. O nome agora se encontra acima de uma segunda linha — uma pequena seta para trás e "Ir para casa", peso regular — e Ambas as pilhas à direita do logótipo. A linha já é o link, então "Ir para casa" é texto simples em vez de um aninhado âncora: um destino, um elemento interativo.
· kamo-internal
Título da página "{Org} - {role} Portal", pintada antes da primeira moldura
Um membro é nomeado pela própria palavra da organização para um — "Harmony - Emprestador Portal" para um credor que os chama de Emprestadores. Um membro da equipa é nomeado pelo seu DEPARTAMENTO — "Del Taco - Portal de Vendas" — que é o facto mais específico e o Um com o qual se identifiquem. A maioria dos membros da equipa não têm departamento, por isso, isso recua. para a etiqueta equipe-membro da organização em vez de para um branco: a forma do título permanece o mesmo para todos, e uma org que renomeou o conceito ainda vê o seu próprio vocabulário. Composto em uma função. Esta superfície quebrou repetidamente de vários escritores cada um possuindo um fragmento e qualquer que tenha pousado última vitória, então comporTabTitle é o que é o carregador, a semente de entrada, o script pré-pintura (através do TheME TITLE KEY) e o piscador de atenção todos acabam usando. Não há segunda cópia do formato. Pintado antes do primeiro quadro, não após uma viagem de ida e volta. O título composto entra em THEME TITLE KEY, que o script na cabeça do documento já se aplica antes de Reagir existe, então cada carga após a primeira aba é instantânea. A primeira entrada é abrangida por: semeando-o durante /validate — dentro da pausa que já precede o redirecionamento, portanto, não custa latência adicional — e é o melhor esforço, pois o aplicativo compõe mesmo título na chegada, independentemente. O título armazenado permanece um MIRROR e nunca se torna a fonte da verdade: cada a carga autorizada recompõe-se e substitui-a. Uma atualização feita em outra aba, ou por um administrador, portanto, corrige-se dentro de uma carga. Essa distinção é a diferença entre isso e as duas falhas de cache fixou-se mais cedo. O carregador lê a sessão do usuárioInfoStore diretamente em vez de através useUserInfo, que também precisa de OrgContext — e este componente é renderizado acima do fornecedores. Até que a sessão resolva o nome da organização permanece sozinho: título incompleto é correto, onde um papel adivinhado não seria.
· SecurityService
Leve o departamento do membro da equipe na sessão
A aba do navegador está sendo renomeada "{Org} - {role} Portal", e um membro da equipe papel é o seu departamento — a coisa específica que eles identificam com dia a dia ("Vendas"), em vez da palavra genérica para pessoal. O departamento está no subtipo de membro da equipe, não no membro, então ele precisa da adesão isso adiciona; a consulta já alcançou member team member para member type, então é mais uma saída esquerda em vez de uma segunda viagem. Escrito na sessão porque é aí que o cliente lê este tipo de coisa a partir de: o /api/user-info do kamo-internal serve diretamente o blob da sessão do Redis, O que não está na bolha custa a cada consumidor um pedido separado para ele. Omitido em vez de armazenado como nulo quando o membro não tem departamento, que é o Caso COMUM — 67 de 83 linhas de produção de membros da equipa não têm nenhuma. Um teste ao consumidor Apenas para a presença, de outra forma, tornaria a ausência como título de trabalho de alguém. A o conjunto de carga útil é extraído para buildTeamMemberPayload para que uma regra seja testável sem levantar-se Redis. Sessões cunhadas antes disto não carregam nenhum departamento e permanecem válidas; a aba cai para trás para o próprio selo equipe-membro da organização, exatamente como ele faz para a maioria que não têm nenhum departamento.
· InitializerService
Mantenha cada roteamento de domínio ao vivo quando a verificação se tornar obrigatória
Host para resolução de organização está prestes a exigir propriedade verified no linha de domínio de topo, de modo que um domínio várias organizações tenham apenas nomeado rotas para aquele que provou o controle de seu DNS. Aplicado à propriedade como está, que filtro levaria cada organização cuja linha precede o desafio TXT fora do ar imediatamente — a bandeira era opcional antes, então a maioria dos domínios vivos simplesmente não o carregam. Isto concede-o aos que já demonstraram que resolvem aqui: DNS verificado, um certificado confirmado ou já emitido, ou um host *.kamocrm.com, que é nosso por construção e que o cliente não poderia adicionar um registro TXT de qualquer maneira. Um domínio sem nada disso nunca foi mostrado para resolver aqui, então sem tráfego pode estar alcançando-o e deixando-o não verificado não quebra nada. Isso é exatamente o Caso a nova regra exista. Deve correr antes das naves filtrantes de biblioteca partilhada.

Seu universo está esperando.
Comece gratuitamente em minutos.Adicione produtos sempre que estiver pronto — sem migração, sem novos logins.
Gratuito para sempre — não é necessário cartão de crédito